From b9e1b109a8278a784c4270afb2d183a54414fd4e Mon Sep 17 00:00:00 2001 From: DavHau Date: Mon, 7 Apr 2025 19:01:42 +0700 Subject: [PATCH] inventory tests: improve framework to set better defaults --- checks/dummy-inventory-test/default.nix | 9 +- .../dummy-module/roles/admin.nix | 2 +- .../dummy-module/roles/peer.nix | 2 +- .../dummy-module/{roles => }/shared.nix | 0 .../dummy-module/update-vars.py | 110 ++++++++++++++++++ checks/lib/test-inventory.nix | 27 ++--- 6 files changed, 132 insertions(+), 18 deletions(-) rename checks/dummy-inventory-test/dummy-module/{roles => }/shared.nix (100%) create mode 100755 checks/dummy-inventory-test/dummy-module/update-vars.py diff --git a/checks/dummy-inventory-test/default.nix b/checks/dummy-inventory-test/default.nix index 240f0654e..582a0e1af 100644 --- a/checks/dummy-inventory-test/default.nix +++ b/checks/dummy-inventory-test/default.nix @@ -3,7 +3,8 @@ { name = "dummy-inventory-test"; - inventory = { + inventory.directory = ./.; + inventory.inventory = { machines.peer1 = { }; machines.admin1 = { }; services = { @@ -19,8 +20,10 @@ testScript = '' start_all() - admin1.wait_for_unit("dummy-service") - peer1.wait_for_unit("dummy-service") + admin1.wait_for_unit("multi-user.target") + peer1.wait_for_unit("multi-user.target") + print(admin1.succeed("systemctl status dummy-service")) + print(peer1.succeed("systemctl status dummy-service")) ''; } ) diff --git a/checks/dummy-inventory-test/dummy-module/roles/admin.nix b/checks/dummy-inventory-test/dummy-module/roles/admin.nix index f3f24bee2..a56780406 100644 --- a/checks/dummy-inventory-test/dummy-module/roles/admin.nix +++ b/checks/dummy-inventory-test/dummy-module/roles/admin.nix @@ -1,5 +1,5 @@ { imports = [ - ./shared.nix + ../shared.nix ]; } diff --git a/checks/dummy-inventory-test/dummy-module/roles/peer.nix b/checks/dummy-inventory-test/dummy-module/roles/peer.nix index f3f24bee2..a56780406 100644 --- a/checks/dummy-inventory-test/dummy-module/roles/peer.nix +++ b/checks/dummy-inventory-test/dummy-module/roles/peer.nix @@ -1,5 +1,5 @@ { imports = [ - ./shared.nix + ../shared.nix ]; } diff --git a/checks/dummy-inventory-test/dummy-module/roles/shared.nix b/checks/dummy-inventory-test/dummy-module/shared.nix similarity index 100% rename from checks/dummy-inventory-test/dummy-module/roles/shared.nix rename to checks/dummy-inventory-test/dummy-module/shared.nix diff --git a/checks/dummy-inventory-test/dummy-module/update-vars.py b/checks/dummy-inventory-test/dummy-module/update-vars.py new file mode 100755 index 000000000..cd632dbac --- /dev/null +++ b/checks/dummy-inventory-test/dummy-module/update-vars.py @@ -0,0 +1,110 @@ +#! /usr/bin/env python3 + +import json +import os +import subprocess +from pathlib import Path +from typing import Any + +from clan_cli.flake import Flake +from clan_cli.machines.machines import Machine +from clan_cli.nix import nix_build, nix_config, nix_eval +from clan_cli.vars.generate import generate_vars +from clan_cli.vars.keygen import keygen + +if _project_root := os.environ.get("PRJ_ROOT"): + clan_core_dir = Path(_project_root) +else: + msg = "PRJ_ROOT not set. Enter the dev environment first" + raise Exception(msg) # noqa TRY002 + +test_name = "dummy-inventory-test" + + +def test_dir(test_name: str) -> Path: + return Path(clan_core_dir / "checks" / test_name) + + +class TestMachine(Machine): + """ + Machine class which is able to deal with not having an actual flake. + All nix build and eval calls will be forwarded to: + clan-core#checks...nodes.. + """ + + @property + def deployment(self) -> dict: + if getattr(self, "_deployment", None): + return self._deployment + cmd = nix_build( + [ + f"{clan_core_dir}#checks.{nix_config()['system']}.{test_name}.nodes.{self.name}.config.system.clan.deployment.file" + ] + ) + out = subprocess.run(cmd, check=True, text=True, stdout=subprocess.PIPE) + self._deployment = json.loads(Path(out.stdout.strip()).read_text()) + return self._deployment + + def eval_nix( + self, + attr: str, + refresh: bool = False, + extra_config: None | dict = None, + nix_options: list[str] | None = None, + ) -> Any: + """ + eval a nix attribute of the machine + @attr: the attribute to get + """ + + if nix_options is None: + nix_options = [] + + # return self.nix("eval", attr, nix_options) + cmd = nix_eval( + [ + f"{clan_core_dir}#checks.{nix_config()['system']}.{test_name}.nodes.{self.name}.{attr}" + ] + ) + out = subprocess.run(cmd, check=True, text=True, stdout=subprocess.PIPE) + return json.loads(out.stdout.strip()) + + def build_nix( + self, + attr: str, + extra_config: None | dict = None, + nix_options: list[str] | None = None, + ) -> Path: + """ + build a nix attribute of the machine + @attr: the attribute to get + """ + + if nix_options is None: + nix_options = [] + + cmd = nix_build( + [ + f"{clan_core_dir}#checks.{nix_config()['system']}.{test_name}.nodes.{self.name}.{attr}" + ] + ) + out = subprocess.run(cmd, check=True, text=True, stdout=subprocess.PIPE) + return Path(out.stdout.strip()) + + def flush_caches(self) -> None: + """ + Disable flush, because it calls prefetch() which resets the overridden Flake._path + """ + return + + +if __name__ == "__main__": + os.environ["CLAN_NO_COMMIT"] = "1" + flake = Flake(str(test_dir(test_name))) + flake._path = test_dir(test_name) # noqa SLF001 + flake._is_local = True # noqa SLF001 + machine = TestMachine("admin1", flake) + user = "admin" + if not Path(flake.path / "sops" / "users" / user / "key.json").exists(): + keygen(user, flake, False) + generate_vars([machine]) diff --git a/checks/lib/test-inventory.nix b/checks/lib/test-inventory.nix index 77d39a625..9950960a5 100644 --- a/checks/lib/test-inventory.nix +++ b/checks/lib/test-inventory.nix @@ -3,35 +3,36 @@ test: let inherit (pkgs) lib; inherit (lib) mkOption flip mapAttrs; - inherit (lib.types) raw; + inherit (lib.types) path raw; inherit (self.lib.inventory) buildInventory; nixos-lib = import (pkgs.path + "/nixos/lib") { }; in (nixos-lib.runTest ( { config, ... }: + let + serviceConfigs = buildInventory { + inventory = config.inventory.inventory; + # TODO: make directory argument optional in buildInventory + directory = config.inventory.directory; + }; + in { imports = [ test ]; options = { - inventory = mkOption { + inventory.inventory = mkOption { description = "Inventory of machines and services"; type = raw; }; - serviceConfigs = mkOption { - description = "Result of the evaluated inventory"; - type = raw; - readOnly = true; + inventory.directory = mkOption { + description = "Directory which contains the vars"; + type = path; }; }; config = { - serviceConfigs = buildInventory { - inventory = config.inventory; - # TODO: make directory argument optional in buildInventory - directory = ./.; - }; - nodes = flip mapAttrs config.serviceConfigs.machines ( + nodes = flip mapAttrs serviceConfigs.machines ( machineName: attrs: { imports = attrs.machineImports ++ [ self.nixosModules.clanCore ]; - clan.core.settings.directory = builtins.toString ./.; + clan.core.settings.directory = toString config.inventory.directory; clan.core.settings.machine.name = machineName; } );