refactor: remove deployment.json and use direct selectors

- Remove deployment.json file generation from outputs.nix
- Add throw for deprecated deployment.file usage with upgrade instructions
- Remove vars data from deployment.data
- Update Machine class to use direct select() calls instead of deployment property
- Update all deployment property accesses to use direct selectors
- Add precaching for frequently accessed values in update.py:
  - Module paths for facts and vars
  - Deployment settings (requireExplicitUpdate, nixosMobileWorkaround)
  - Services and generators data
  - Secret upload locations
- This removes unnecessary JSON serialization and makes the code more composable
This commit is contained in:
lassulus
2025-06-29 19:00:38 +02:00
parent 40cd8672f1
commit a055b4d1eb
15 changed files with 62 additions and 55 deletions

View File

@@ -22,7 +22,6 @@
dependencies = [
self
pkgs.stdenv.drvPath
self.clan.clanInternals.machines.${pkgs.hostPlatform.system}.test-backup.config.system.clan.deployment.file
] ++ builtins.map (i: i.outPath) (builtins.attrValues self.inputs);
closureInfo = pkgs.closureInfo { rootPaths = dependencies; };
in

View File

@@ -50,7 +50,6 @@
self.nixosConfigurations."test-flash-machine-${pkgs.hostPlatform.system}".config.system.build.toplevel
self.nixosConfigurations."test-flash-machine-${pkgs.hostPlatform.system}".config.system.build.diskoScript
self.nixosConfigurations."test-flash-machine-${pkgs.hostPlatform.system}".config.system.build.diskoScript.drvPath
self.nixosConfigurations."test-flash-machine-${pkgs.hostPlatform.system}".config.system.clan.deployment.file
] ++ builtins.map (i: i.outPath) (builtins.attrValues self.inputs);
closureInfo = pkgs.closureInfo { rootPaths = dependencies; };
in

View File

@@ -10,7 +10,6 @@ let
dependencies = [
self.clan.clanInternals.machines.${pkgs.hostPlatform.system}.test-install-machine-with-system.config.system.build.toplevel
self.clan.clanInternals.machines.${pkgs.hostPlatform.system}.test-install-machine-with-system.config.system.build.diskoScript
self.clan.clanInternals.machines.${pkgs.hostPlatform.system}.test-install-machine-with-system.config.system.clan.deployment.file
pkgs.stdenv.drvPath
pkgs.bash.drvPath
pkgs.nixos-anywhere

View File

@@ -35,7 +35,6 @@
pkgs.stdenv.drvPath
pkgs.stdenvNoCC
self.nixosConfigurations.test-morph-machine.config.system.build.toplevel
self.nixosConfigurations.test-morph-machine.config.system.clan.deployment.file
] ++ builtins.map (i: i.outPath) (builtins.attrValues self.inputs);
closureInfo = pkgs.closureInfo { rootPaths = dependencies; };
in

View File

@@ -1,7 +1,6 @@
{
config,
lib,
pkgs,
...
}:
{
@@ -24,6 +23,14 @@
description = ''
the location of the deployment.json file
'';
default = throw ''
deployment.json file generation has been removed in favor of direct selectors.
Please upgrade your clan-cli to the latest version.
The deployment data is now accessed directly from the configuration
instead of being written to a separate JSON file.
'';
};
deployment.buildHost = lib.mkOption {
type = lib.types.nullOr lib.types.str;
@@ -83,8 +90,5 @@
inherit (config.clan.core.deployment) requireExplicitUpdate;
inherit (config.system.clan.deployment) nixosMobileWorkaround;
};
system.clan.deployment.file = pkgs.writeText "deployment.json" (
builtins.toJSON config.system.clan.deployment.data
);
};
}

View File

@@ -73,10 +73,5 @@ in
) [ ] (lib.attrValues generator.files)
) [ ] (lib.attrValues config.clan.core.vars.generators);
system.clan.deployment.data = {
vars = config.clan.core.vars._serialized;
inherit (config.clan.core.networking) targetHost buildHost;
inherit (config.clan.core.deployment) requireExplicitUpdate;
};
};
}

View File

@@ -64,8 +64,6 @@ in
};
};
config = {
system.clan.deployment.data.password-store.secretLocation =
config.clan.vars.password-store.secretLocation;
clan.core.vars.settings =
lib.mkIf (config.clan.core.vars.settings.secretStore == "password-store")
{

View File

@@ -31,7 +31,7 @@ class SecretStore(SecretStoreBase):
sops_secrets_folder(self.machine.flake_dir)
/ f"{self.machine.name}-age.key",
priv_key,
add_groups=self.machine.deployment["sops"]["defaultGroups"],
add_groups=self.machine.select("config.clan.core.sops.defaultGroups"),
age_plugins=load_age_plugins(self.machine.flake),
)
add_machine(self.machine.flake_dir, self.machine.name, pub_key, False)

View File

@@ -58,8 +58,11 @@ def update_command(args: argparse.Namespace) -> None:
raise ClanError(msg)
def filter_machine(m: Machine) -> bool:
if m.deployment.get("requireExplicitUpdate", False):
try:
if m.select("config.clan.deployment.requireExplicitUpdate"):
return False
except Exception:
pass
try:
# check if the machine has a target host set
@@ -96,7 +99,17 @@ def update_command(args: argparse.Namespace) -> None:
args.flake.precache(
[
f"clanInternals.machines.{system}.{{{','.join(machine_names)}}}.config.clan.core.vars.generators.*.validationHash",
f"clanInternals.machines.{system}.{{{','.join(machine_names)}}}.config.system.clan.deployment.file",
f"clanInternals.machines.{system}.{{{','.join(machine_names)}}}.config.clan.deployment.requireExplicitUpdate",
f"clanInternals.machines.{system}.{{{','.join(machine_names)}}}.config.system.clan.deployment.nixosMobileWorkaround",
f"clanInternals.machines.{system}.{{{','.join(machine_names)}}}.config.clan.core.facts.secretModule",
f"clanInternals.machines.{system}.{{{','.join(machine_names)}}}.config.clan.core.facts.publicModule",
f"clanInternals.machines.{system}.{{{','.join(machine_names)}}}.config.clan.core.vars.settings.secretModule",
f"clanInternals.machines.{system}.{{{','.join(machine_names)}}}.config.clan.core.vars.settings.publicModule",
f"clanInternals.machines.{system}.{{{','.join(machine_names)}}}.config.clan.core.facts.services",
f"clanInternals.machines.{system}.{{{','.join(machine_names)}}}.config.clan.core.vars._serialized.generators",
f"clanInternals.machines.{system}.{{{','.join(machine_names)}}}.config.clan.core.facts.secretUploadDirectory",
f"clanInternals.machines.{system}.{{{','.join(machine_names)}}}.config.clan.vars.password-store.secretLocation",
f"clanInternals.machines.{system}.{{{','.join(machine_names)}}}.config.clan.core.vars.settings.passBackend",
]
)

View File

@@ -535,7 +535,6 @@ def generate_command(args: argparse.Namespace) -> None:
args.flake.precache(
[
f"clanInternals.machines.{system}.{{{','.join(machine_names)}}}.config.clan.core.vars.generators.*.validationHash",
f"clanInternals.machines.{system}.{{{','.join(machine_names)}}}.config.system.clan.deployment.file",
]
)
has_changed = generate_vars(

View File

@@ -153,7 +153,7 @@ class SecretStore(StoreBase):
# TODO get the path to the secrets from the machine
[
"cat",
f"{self.machine.deployment['password-store']['secretLocation']}/.{self._store_backend}_info",
f"{self.machine.select('config.clan.vars.password-store.secretLocation')}/.{self._store_backend}_info",
],
RunOpts(log=Log.STDERR, check=False),
).stdout.strip()
@@ -237,6 +237,6 @@ class SecretStore(StoreBase):
pass_dir = Path(_tempdir).resolve()
self.populate_dir(pass_dir, phases)
upload_dir = Path(
self.machine.deployment["password-store"]["secretLocation"]
self.machine.select("config.clan.vars.password-store.secretLocation")
)
upload(host, pass_dir, upload_dir)

View File

@@ -71,7 +71,7 @@ class SecretStore(StoreBase):
sops_secrets_folder(self.machine.flake_dir)
/ f"{self.machine.name}-age.key",
priv_key,
add_groups=self.machine.deployment["sops"]["defaultGroups"],
add_groups=self.machine.select("config.clan.core.sops.defaultGroups"),
age_plugins=load_age_plugins(self.machine.flake),
)
add_machine(self.machine.flake_dir, self.machine.name, pub_key, False)
@@ -158,7 +158,7 @@ class SecretStore(StoreBase):
secret_folder,
value,
add_machines=[self.machine.name] if var.deploy else [],
add_groups=self.machine.deployment["sops"]["defaultGroups"],
add_groups=self.machine.select("config.clan.core.sops.defaultGroups"),
git_commit=False,
age_plugins=load_age_plugins(self.machine.flake),
)
@@ -259,7 +259,7 @@ class SecretStore(StoreBase):
)
keys = collect_keys_for_path(path)
for group in self.machine.deployment["sops"]["defaultGroups"]:
for group in self.machine.select("config.clan.core.sops.defaultGroups"):
keys.update(
collect_keys_for_type(
self.machine.flake_dir / "sops" / "groups" / group / "machines"
@@ -314,7 +314,7 @@ class SecretStore(StoreBase):
age_plugins = load_age_plugins(self.machine.flake)
for group in self.machine.deployment["sops"]["defaultGroups"]:
for group in self.machine.select("config.clan.core.sops.defaultGroups"):
allow_member(
groups_folder(secret_path),
sops_groups_folder(self.machine.flake_dir),

View File

@@ -1,5 +1,4 @@
import importlib
import json
import logging
import re
from dataclasses import dataclass
@@ -15,7 +14,7 @@ from clan_lib.api import API
from clan_lib.errors import ClanCmdError, ClanError
from clan_lib.flake import Flake
from clan_lib.machines.actions import get_machine
from clan_lib.nix import nix_config, nix_test_store
from clan_lib.nix import nix_config
from clan_lib.nix_models.clan import InventoryMachine
from clan_lib.ssh.remote import Remote
@@ -79,58 +78,57 @@ class Machine:
f'{self._class_}Configurations."{self.name}".pkgs.hostPlatform.system'
)
@property
def deployment(self) -> dict:
output = Path(self.select("config.system.clan.deployment.file"))
if tmp_store := nix_test_store():
output = tmp_store.joinpath(*output.parts[1:])
deployment = json.loads(output.read_text())
return deployment
@cached_property
def secret_facts_store(self) -> facts_secret_modules.SecretStoreBase:
module = importlib.import_module(self.deployment["facts"]["secretModule"])
secret_module = self.select("config.clan.core.facts.secretModule")
module = importlib.import_module(secret_module)
return module.SecretStore(machine=self)
@cached_property
def public_facts_store(self) -> facts_public_modules.FactStoreBase:
module = importlib.import_module(self.deployment["facts"]["publicModule"])
public_module = self.select("config.clan.core.facts.publicModule")
module = importlib.import_module(public_module)
return module.FactStore(machine=self)
@cached_property
def secret_vars_store(self) -> StoreBase:
module = importlib.import_module(self.deployment["vars"]["secretModule"])
secret_module = self.select("config.clan.core.vars.settings.secretModule")
module = importlib.import_module(secret_module)
return module.SecretStore(machine=self)
@cached_property
def public_vars_store(self) -> StoreBase:
module = importlib.import_module(self.deployment["vars"]["publicModule"])
public_module = self.select("config.clan.core.vars.settings.publicModule")
module = importlib.import_module(public_module)
return module.FactStore(machine=self)
@property
def facts_data(self) -> dict[str, dict[str, Any]]:
if self.deployment["facts"]["services"]:
return self.deployment["facts"]["services"]
services = self.select("config.clan.core.facts.services")
if services:
return services
return {}
def vars_generators(self) -> list["Generator"]:
from clan_cli.vars.generate import Generator
clan_vars = self.deployment.get("vars")
if clan_vars is None:
try:
generators_data = self.select(
"config.clan.core.vars._serialized.generators"
)
if generators_data is None:
return []
generators: dict[str, Any] = clan_vars.get("generators")
if generators is None:
return []
_generators = [Generator.from_json(gen) for gen in generators.values()]
_generators = [Generator.from_json(gen) for gen in generators_data.values()]
for gen in _generators:
gen.machine(self)
except Exception:
return []
else:
return _generators
@property
def secrets_upload_directory(self) -> str:
return self.deployment["facts"]["secretUploadDirectory"]
return self.select("config.clan.core.facts.secretUploadDirectory")
@property
def flake_dir(self) -> Path:

View File

@@ -184,7 +184,12 @@ def deploy_machine(
# retry nixos-rebuild switch if the first attempt failed
if ret.returncode != 0:
is_mobile = machine.deployment.get("nixosMobileWorkaround", False)
try:
is_mobile = machine.select(
"config.system.clan.deployment.nixosMobileWorkaround"
)
except Exception:
is_mobile = False
# if the machine is mobile, we retry to deploy with the mobile workaround method
if is_mobile:
machine.info(

View File

@@ -161,7 +161,6 @@ def main() -> None:
flake.precache(
[
f"checks.{test_system}.{opts.check_attr}.machinesCross.{system}.{{{','.join(machine_names)}}}.config.clan.core.vars.generators.*.validationHash",
f"checks.{test_system}.{opts.check_attr}.machinesCross.{system}.{{{','.join(machine_names)}}}.config.system.clan.deployment.file",
]
)