clan-lib: Move nix_options from Machine class to Flake class

This commit is contained in:
Qubasa
2025-06-23 13:46:10 +02:00
parent 20080f8857
commit cd1d49b603
11 changed files with 57 additions and 54 deletions

View File

@@ -35,20 +35,27 @@ with contextlib.suppress(ImportError):
import argcomplete # type: ignore[no-redef] import argcomplete # type: ignore[no-redef]
def flake_path(arg: str) -> Flake: def flake_path(arg: str) -> str:
flake_dir = Path(arg).resolve() flake_dir = Path(arg).resolve()
if flake_dir.exists() and flake_dir.is_dir(): if flake_dir.exists() and flake_dir.is_dir():
return Flake(str(flake_dir)) return str(flake_dir)
return Flake(arg) return arg
def default_flake() -> Flake | None: def default_flake() -> str | None:
val = get_clan_flake_toplevel_or_env() val = get_clan_flake_toplevel_or_env()
if val: if val:
return Flake(str(val)) return str(val)
return None return None
def create_flake_from_args(args: argparse.Namespace) -> Flake:
"""Create a Flake object from parsed arguments, including nix_options."""
flake_path_str = args.flake
nix_options = getattr(args, "option", [])
return Flake(flake_path_str, nix_options=nix_options)
def add_common_flags(parser: argparse.ArgumentParser) -> None: def add_common_flags(parser: argparse.ArgumentParser) -> None:
def argument_exists(parser: argparse.ArgumentParser, arg: str) -> bool: def argument_exists(parser: argparse.ArgumentParser, arg: str) -> bool:
""" """
@@ -450,6 +457,10 @@ def main() -> None:
if not hasattr(args, "func"): if not hasattr(args, "func"):
return return
# Convert flake path to Flake object with nix_options if flake argument exists
if hasattr(args, "flake") and args.flake is not None:
args.flake = create_flake_from_args(args)
try: try:
args.func(args) args.func(args)
except ClanError as e: except ClanError as e:

View File

@@ -42,7 +42,7 @@ def install_command(args: argparse.Namespace) -> None:
else: else:
password = None password = None
machine = Machine(name=args.machine, flake=args.flake, nix_options=args.option) machine = Machine(name=args.machine, flake=args.flake)
host_key_check = args.host_key_check host_key_check = args.host_key_check
if target_host_str is not None: if target_host_str is not None:

View File

@@ -46,9 +46,7 @@ def update_command(args: argparse.Namespace) -> None:
raise ClanError(msg) raise ClanError(msg)
for machine_name in selected_machines: for machine_name in selected_machines:
machine = Machine( machine = Machine(name=machine_name, flake=args.flake)
name=machine_name, flake=args.flake, nix_options=args.option
)
machines.append(machine) machines.append(machine)
if args.target_host is not None and len(machines) > 1: if args.target_host is not None and len(machines) > 1:

View File

@@ -2,7 +2,7 @@ import argparse
import logging import logging
import shlex import shlex
from clan_cli import create_parser from clan_cli import create_flake_from_args, create_parser
from clan_lib.custom_logger import print_trace from clan_lib.custom_logger import print_trace
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@@ -13,6 +13,10 @@ def run(args: list[str]) -> argparse.Namespace:
parsed = parser.parse_args(args) parsed = parser.parse_args(args)
cmd = shlex.join(["clan", *args]) cmd = shlex.join(["clan", *args])
# Convert flake path to Flake object with nix_options if flake argument exists
if hasattr(parsed, "flake") and parsed.flake is not None:
parsed.flake = create_flake_from_args(parsed)
print_trace(f"$ {cmd}", log, "localhost") print_trace(f"$ {cmd}", log, "localhost")
if hasattr(parsed, "func"): if hasattr(parsed, "func"):
parsed.func(parsed) parsed.func(parsed)

View File

@@ -511,7 +511,7 @@ def generate_command(args: argparse.Namespace) -> None:
msg = "Could not find clan flake toplevel directory" msg = "Could not find clan flake toplevel directory"
raise ClanError(msg) raise ClanError(msg)
machines: list[Machine] = list(list_full_machines(args.flake, args.option).values()) machines: list[Machine] = list(list_full_machines(args.flake).values())
if len(args.machines) > 0: if len(args.machines) > 0:
machines = list( machines = list(

View File

@@ -576,6 +576,7 @@ class Flake:
identifier: str identifier: str
hash: str | None = None hash: str | None = None
store_path: str | None = None store_path: str | None = None
nix_options: list[str] | None = None
_flake_cache_path: Path | None = field(init=False, default=None) _flake_cache_path: Path | None = field(init=False, default=None)
_cache: FlakeCache | None = field(init=False, default=None) _cache: FlakeCache | None = field(init=False, default=None)
@@ -583,8 +584,13 @@ class Flake:
_is_local: bool | None = field(init=False, default=None) _is_local: bool | None = field(init=False, default=None)
@classmethod @classmethod
def from_json(cls: type["Flake"], data: dict[str, Any]) -> "Flake": def from_json(
return cls(data["identifier"]) cls: type["Flake"],
data: dict[str, Any],
*,
nix_options: list[str] | None = None,
) -> "Flake":
return cls(data["identifier"], nix_options=nix_options)
def __str__(self) -> str: def __str__(self) -> str:
return self.identifier return self.identifier
@@ -632,10 +638,14 @@ class Flake:
nix_command, nix_command,
) )
if self.nix_options is None:
self.nix_options = []
cmd = [ cmd = [
"flake", "flake",
"prefetch", "prefetch",
"--json", "--json",
*self.nix_options,
"--option", "--option",
"flake-registry", "flake-registry",
"", "",
@@ -690,7 +700,6 @@ class Flake:
def get_from_nix( def get_from_nix(
self, self,
selectors: list[str], selectors: list[str],
nix_options: list[str] | None = None,
apply: str = "v: v", apply: str = "v: v",
) -> None: ) -> None:
""" """
@@ -722,8 +731,7 @@ class Flake:
self.invalidate_cache() self.invalidate_cache()
assert self._cache is not None assert self._cache is not None
if nix_options is None: nix_options = self.nix_options if self.nix_options is not None else []
nix_options = []
str_selectors: list[str] = [] str_selectors: list[str] = []
for selector in selectors: for selector in selectors:
@@ -736,7 +744,9 @@ class Flake:
# method to getting the NAR hash # method to getting the NAR hash
fallback_nixpkgs_hash = "@fallback_nixpkgs_hash@" fallback_nixpkgs_hash = "@fallback_nixpkgs_hash@"
if not fallback_nixpkgs_hash.startswith("sha256-"): if not fallback_nixpkgs_hash.startswith("sha256-"):
fallback_nixpkgs = Flake(str(nixpkgs_source())) fallback_nixpkgs = Flake(
str(nixpkgs_source()), nix_options=self.nix_options
)
fallback_nixpkgs.invalidate_cache() fallback_nixpkgs.invalidate_cache()
assert fallback_nixpkgs.hash is not None, ( assert fallback_nixpkgs.hash is not None, (
"this should be impossible as invalidate_cache() should always set `hash`" "this should be impossible as invalidate_cache() should always set `hash`"
@@ -745,7 +755,7 @@ class Flake:
select_hash = "@select_hash@" select_hash = "@select_hash@"
if not select_hash.startswith("sha256-"): if not select_hash.startswith("sha256-"):
select_flake = Flake(str(select_source())) select_flake = Flake(str(select_source()), nix_options=self.nix_options)
select_flake.invalidate_cache() select_flake.invalidate_cache()
assert select_flake.hash is not None, ( assert select_flake.hash is not None, (
"this should be impossible as invalidate_cache() should always set `hash`" "this should be impossible as invalidate_cache() should always set `hash`"
@@ -808,11 +818,7 @@ class Flake:
if self.flake_cache_path: if self.flake_cache_path:
self._cache.save_to_file(self.flake_cache_path) self._cache.save_to_file(self.flake_cache_path)
def precache( def precache(self, selectors: list[str]) -> None:
self,
selectors: list[str],
nix_options: list[str] | None = None,
) -> None:
""" """
Ensures that the specified selectors are cached locally. Ensures that the specified selectors are cached locally.
@@ -822,7 +828,6 @@ class Flake:
Args: Args:
selectors (list[str]): A list of attribute selectors to check and cache. selectors (list[str]): A list of attribute selectors to check and cache.
nix_options (list[str] | None): Optional additional options to pass to the Nix build command.
""" """
if self._cache is None: if self._cache is None:
self.invalidate_cache() self.invalidate_cache()
@@ -833,12 +838,11 @@ class Flake:
if not self._cache.is_cached(selector): if not self._cache.is_cached(selector):
not_fetched_selectors.append(selector) not_fetched_selectors.append(selector)
if not_fetched_selectors: if not_fetched_selectors:
self.get_from_nix(not_fetched_selectors, nix_options) self.get_from_nix(not_fetched_selectors)
def select( def select(
self, self,
selector: str, selector: str,
nix_options: list[str] | None = None,
apply: str = "v: v", apply: str = "v: v",
) -> Any: ) -> Any:
""" """
@@ -847,7 +851,6 @@ class Flake:
Args: Args:
selector (str): The attribute selector string to fetch the value for. selector (str): The attribute selector string to fetch the value for.
nix_options (list[str] | None): Optional additional options to pass to the Nix build command.
""" """
if self._cache is None: if self._cache is None:
self.invalidate_cache() self.invalidate_cache()
@@ -856,6 +859,6 @@ class Flake:
if not self._cache.is_cached(selector): if not self._cache.is_cached(selector):
log.debug(f"Cache miss for {selector}") log.debug(f"Cache miss for {selector}")
self.get_from_nix([selector], nix_options, apply=apply) self.get_from_nix([selector], apply=apply)
value = self._cache.select(selector) value = self._cache.select(selector)
return value return value

View File

@@ -284,13 +284,14 @@ def test_cache_gc(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
} }
""") """)
my_flake = Flake(str(tmp_path / "flake")) my_flake = Flake(
str(tmp_path / "flake"),
nix_options=["--sandbox-build-dir", str(tmp_path / "build")],
)
if platform == "darwin": if platform == "darwin":
my_flake.select("testfile") my_flake.select("testfile")
else: else:
my_flake.select( my_flake.select("testfile")
"testfile", nix_options=["--sandbox-build-dir", str(tmp_path / "build")]
)
assert my_flake._cache is not None # noqa: SLF001 assert my_flake._cache is not None # noqa: SLF001
assert my_flake._cache.is_cached("testfile") # noqa: SLF001 assert my_flake._cache.is_cached("testfile") # noqa: SLF001
subprocess.run(["nix-collect-garbage"], check=True) subprocess.run(["nix-collect-garbage"], check=True)

View File

@@ -17,9 +17,7 @@ from clan_lib.nix_models.clan import InventoryMachine
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
def list_full_machines( def list_full_machines(flake: Flake) -> dict[str, Machine]:
flake: Flake, nix_options: list[str] | None = None
) -> dict[str, Machine]:
""" """
Like `list_machines`, but returns a full 'machine' instance for each machine. Like `list_machines`, but returns a full 'machine' instance for each machine.
""" """
@@ -27,9 +25,6 @@ def list_full_machines(
res: dict[str, Machine] = {} res: dict[str, Machine] = {}
if nix_options is None:
nix_options = []
for inv_machine in machines.values(): for inv_machine in machines.values():
name = inv_machine.get("name") name = inv_machine.get("name")
# Technically, this should not happen, but we are defensive here. # Technically, this should not happen, but we are defensive here.
@@ -37,11 +32,7 @@ def list_full_machines(
msg = "InternalError: Machine name is required. But got a machine without a name." msg = "InternalError: Machine name is required. But got a machine without a name."
raise ClanError(msg) raise ClanError(msg)
machine = Machine( machine = Machine(name=name, flake=flake)
name=name,
flake=flake,
nix_options=nix_options,
)
res[machine.name] = machine res[machine.name] = machine
return res return res

View File

@@ -2,7 +2,7 @@ import importlib
import json import json
import logging import logging
import re import re
from dataclasses import dataclass, field from dataclasses import dataclass
from functools import cached_property from functools import cached_property
from pathlib import Path from pathlib import Path
from typing import TYPE_CHECKING, Any, Literal from typing import TYPE_CHECKING, Any, Literal
@@ -30,8 +30,6 @@ class Machine:
name: str name: str
flake: Flake flake: Flake
nix_options: list[str] = field(default_factory=list)
def get_inv_machine(self) -> "InventoryMachine": def get_inv_machine(self) -> "InventoryMachine":
return get_machine(self.flake, self.name) return get_machine(self.flake, self.name)
@@ -177,8 +175,7 @@ class Machine:
system = config["system"] system = config["system"]
return self.flake.select( return self.flake.select(
f'clanInternals.machines."{system}"."{self.name}".{attr}', f'clanInternals.machines."{system}"."{self.name}".{attr}'
nix_options=nix_options,
) )
def eval_nix( def eval_nix(

View File

@@ -124,6 +124,8 @@ def deploy_machine(
path = upload_sources(machine, sudo_host) path = upload_sources(machine, sudo_host)
nix_options = machine.flake.nix_options if machine.flake.nix_options else []
nix_options = [ nix_options = [
"--show-trace", "--show-trace",
"--option", "--option",
@@ -133,7 +135,7 @@ def deploy_machine(
"accept-flake-config", "accept-flake-config",
"true", "true",
"-L", "-L",
*machine.nix_options, *nix_options,
"--flake", "--flake",
f"{path}#{machine.name}", f"{path}#{machine.name}",
] ]

View File

@@ -86,11 +86,7 @@ class WriteInfo:
class FlakeInterface(Protocol): class FlakeInterface(Protocol):
def select( def select(self, selector: str) -> Any: ...
self,
selector: str,
nix_options: list[str] | None = None,
) -> Any: ...
def invalidate_cache(self) -> None: ... def invalidate_cache(self) -> None: ...