From 23270ff0ce2fa2c1b3e984a77d3277b2beb0ff19 Mon Sep 17 00:00:00 2001 From: Johannes Kirschbauer Date: Tue, 15 Apr 2025 22:36:43 +0200 Subject: [PATCH 1/4] feat(scripts/update-vars): expose all configurables as arguments {repo_root, test_dir, check_attr} --- pkgs/scripts/update-vars.py | 61 +++++++++++++++++++++++-------------- 1 file changed, 38 insertions(+), 23 deletions(-) diff --git a/pkgs/scripts/update-vars.py b/pkgs/scripts/update-vars.py index 40c51910e..4a5a11a76 100755 --- a/pkgs/scripts/update-vars.py +++ b/pkgs/scripts/update-vars.py @@ -13,29 +13,19 @@ 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 -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 - sops_priv_key = ( "AGE-SECRET-KEY-1PL0M9CWRCG3PZ9DXRTTLMCVD57U6JDFE8K7DNVQ35F4JENZ6G3MQ0RQLRV" ) sops_pub_key = "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg" -def _test_dir(test_name: str) -> Path: - return Path(clan_core_dir / "checks" / test_name) - - -def machine_names(test_name: str) -> list[str]: +def machine_names(repo_root: Path, check_attr: str) -> list[str]: """ Get the machine names from the test flake """ cmd = nix_eval( [ - f"{clan_core_dir}#checks.{nix_config()['system']}.{test_name}.nodes", + f"{repo_root}#checks.{nix_config()['system']}.{check_attr}.nodes", "--apply", "builtins.attrNames", ] @@ -51,9 +41,9 @@ class TestMachine(Machine): clan-core#checks...nodes.. """ - def __init__(self, name: str, flake: Flake, test_name: str) -> None: + def __init__(self, name: str, flake: Flake, check_attr: str) -> None: super().__init__(name, flake) - self.test_name = test_name + self.check_attr = check_attr @property def deployment(self) -> dict: @@ -61,7 +51,7 @@ class TestMachine(Machine): return self._deployment cmd = nix_build( [ - f"{clan_core_dir}#checks.{nix_config()['system']}.{self.test_name}.nodes.{self.name}.system.clan.deployment.file" + f"{self.flake.path}#checks.{nix_config()['system']}.{self.check_attr}.nodes.{self.name}.system.clan.deployment.file" ] ) out = subprocess.run(cmd, check=True, text=True, stdout=subprocess.PIPE) @@ -86,7 +76,7 @@ class TestMachine(Machine): # return self.nix("eval", attr, nix_options) cmd = nix_eval( [ - f"{clan_core_dir}#checks.{nix_config()['system']}.{self.test_name}.nodes.{self.name}.{attr}" + f"{self.flake.path}#checks.{nix_config()['system']}.{self.check_attr}.nodes.{self.name}.{attr}" ] ) out = subprocess.run(cmd, check=True, text=True, stdout=subprocess.PIPE) @@ -108,7 +98,7 @@ class TestMachine(Machine): cmd = nix_build( [ - f"{clan_core_dir}#checks.{nix_config()['system']}.{self.test_name}.nodes.{self.name}.{attr}" + f"{self.flake.path}#checks.{nix_config()['system']}.{self.check_attr}.nodes.{self.name}.{attr}" ] ) out = subprocess.run(cmd, check=True, text=True, stdout=subprocess.PIPE) @@ -125,12 +115,34 @@ def parse_args() -> argparse.Namespace: import argparse parser = argparse.ArgumentParser( - description="Update the vars of an inventory test", + description=""" + Update the vars of a 'makeTestClan' integration test. + See 'clanLib.test.makeTestClan' for more information on how to create such a test. + """, ) parser.add_argument( - "test_name", + "--repo_root", + type=Path, + help=""" + Should be an absolute path to the repo root. + This path is used as root to evaluate and build attributes using the nix commands. + i.e. 'nix eval #checks ...' + """, + required=False, + default=os.environ.get("PRJ_ROOT"), + ) + parser.add_argument( + "test_dir", + type=Path, + help=""" + The folder of the test. Usually passed as 'directory' to clan in the test. + Must be relative to the repo_root. + """, + ) + parser.add_argument( + "check_attr", type=str, - help="The name of the test to update", + help="The attribute name of the flake#checks to update", ) return parser.parse_args() @@ -138,14 +150,17 @@ def parse_args() -> argparse.Namespace: if __name__ == "__main__": os.environ["CLAN_NO_COMMIT"] = "1" args = parse_args() - test_dir = _test_dir(args.test_name) + test_dir = args.repo_root / args.test_dir subprocess.run(["rm", "-rf", f"{test_dir}/vars", f"{test_dir}/sops"]) flake = Flake(str(test_dir)) flake._path = test_dir # noqa SLF001 flake._is_local = True # noqa SLF001 machines = [ - TestMachine(name, flake, args.test_name) - for name in machine_names(args.test_name) + TestMachine(name, flake, args.check_attr) + for name in machine_names( + args.repo_root, + args.check_attr, + ) ] user = "admin" admin_key_path = Path(flake.path / "sops" / "users" / user / "key.json") From 7dec3b811782caf2ea2258d588768b1fb3086b86 Mon Sep 17 00:00:00 2001 From: Johannes Kirschbauer Date: Wed, 16 Apr 2025 10:02:20 +0200 Subject: [PATCH 2/4] fixup: update commen in inventory example test --- checks/dummy-inventory-test/default.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/checks/dummy-inventory-test/default.nix b/checks/dummy-inventory-test/default.nix index 46e42ecec..2a1b9bb17 100644 --- a/checks/dummy-inventory-test/default.nix +++ b/checks/dummy-inventory-test/default.nix @@ -40,7 +40,7 @@ clanLib.test.makeTestClan { perMachine = { nixosModule = { # This should be generated by: - # ./pkgs/scripts/update-vars.py inventory-test-framework-compatibility-test + # ./pkgs/scripts/update-vars.py clan.core.vars.generators.new-service = { files.hello = { secret = false; From 7bdb2cabf9e7d459de766beb6a28eac1d8c35c55 Mon Sep 17 00:00:00 2001 From: Johannes Kirschbauer Date: Wed, 16 Apr 2025 10:04:31 +0200 Subject: [PATCH 3/4] fix: rename folder lib/tests -> lib/test to be consistent with the attribute name --- lib/{tests => test}/default.nix | 0 lib/{tests => test}/minify.nix | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename lib/{tests => test}/default.nix (100%) rename lib/{tests => test}/minify.nix (100%) diff --git a/lib/tests/default.nix b/lib/test/default.nix similarity index 100% rename from lib/tests/default.nix rename to lib/test/default.nix diff --git a/lib/tests/minify.nix b/lib/test/minify.nix similarity index 100% rename from lib/tests/minify.nix rename to lib/test/minify.nix From 27960382548ed9a730612b95a7c52c18e87d0b6d Mon Sep 17 00:00:00 2001 From: Johannes Kirschbauer Date: Wed, 16 Apr 2025 10:33:32 +0200 Subject: [PATCH 4/4] chore(test/makeTestClan): document all options, remove magic specialArgs --- lib/default.nix | 2 +- lib/test/default.nix | 69 ++++++++++++++++++++++++++++++-------------- lib/test/sops.nix | 16 ++++++++++ 3 files changed, 65 insertions(+), 22 deletions(-) create mode 100644 lib/test/sops.nix diff --git a/lib/default.nix b/lib/default.nix index 9e852e89e..53e701e22 100644 --- a/lib/default.nix +++ b/lib/default.nix @@ -25,7 +25,7 @@ lib.fix (clanLib: { buildClanModule = clanLib.callLib ./build-clan { }; inventory = clanLib.callLib ./inventory { }; modules = clanLib.callLib ./inventory/frontmatter { }; - test = clanLib.callLib ./tests { }; + test = clanLib.callLib ./test { }; # Plain imports. values = import ./introspection { inherit lib; }; diff --git a/lib/test/default.nix b/lib/test/default.nix index d048c9ed1..072a45801 100644 --- a/lib/test/default.nix +++ b/lib/test/default.nix @@ -7,6 +7,7 @@ let in { minifyModule = ./minify.nix; + sopsModule = ./sops.nix; # A function that returns an extension to runTest makeTestClan = { @@ -54,38 +55,64 @@ in }; }; config = { + # Inherit all nodes from the clan + # i.e. nodes.jon <- clan.machines.jon + # clanInternals.nixosModules contains nixosModules per node nodes = clanFlakeResult.clanInternals.nixosModules; + hostPkgs = pkgs; - # speed-up evaluation + + # !WARNING: Write a detailed comment if adding new options here + # We should be very careful about adding new options here because it affects all tests + # Keep in mind: + # - tests should be close to the real world as possible + # - ensure stability: in clan-core and downstream + # - ensure that the tests are fast and reliable defaults = ( { config, ... }: { imports = [ + # Speed up evaluation clanLib.test.minifyModule - ]; - documentation.enable = lib.mkDefault false; - nix.settings.min-free = 0; - system.stateVersion = config.system.nixos.release; - boot.initrd.systemd.enable = false; - # setup for sops - sops.age.keyFile = "/run/age-key.txt"; - system.activationScripts = - { - setupSecrets.deps = [ "age-key" ]; - age-key.text = '' - echo AGE-SECRET-KEY-1PL0M9CWRCG3PZ9DXRTTLMCVD57U6JDFE8K7DNVQ35F4JENZ6G3MQ0RQLRV > /run/age-key.txt - ''; - } - // lib.optionalAttrs (lib.filterAttrs (_: v: v.neededForUsers) config.sops.secrets != { }) { - setupSecretsForUsers.deps = [ "age-key" ]; - }; + # Setup for sops during tests + # configures a static age-key to skip the age-key generation + clanLib.test.sopsModule + ]; + + # Disable documentation + # This is nice to speed up the evaluation + # And also suppresses any warnings or errors about the documentation + documentation.enable = lib.mkDefault false; + + # Disable garbage collection during the test + # https://nix.dev/manual/nix/2.28/command-ref/conf-file.html?highlight=min-free#available-settings + nix.settings.min-free = 0; + + # This is typically set once via vars generate for a machine + # Since we have ephemeral machines, we set it here for the test + system.stateVersion = config.system.nixos.release; + + # Currently this is the default in NixOS, but we set it explicitly to avoid surprises + # Disable the initrd systemd service which has the following effect + # + # With the below on 'false' initrd runs a 'minimal shell script', called the stage-1 init. + # Benefits: + # Simple and fast. + # Easier to debug for very minimal or custom setups. + # Drawbacks: + # Limited flexibility. + # Harder to handle advanced setups (like TPM, LUKS, or LVM-on-LUKS) but not needed since we are in a test + # No systemd journal logs from initrd. + boot.initrd.systemd.enable = false; } ); - # to accept external dependencies such as disko - _module.args = { inherit self; }; - node.specialArgs.self = self; + # TODO: figure out if we really need this + # I am proposing for less magic in the test-framework + # People may add this in their own tests + # _module.args = { inherit self; }; + # node.specialArgs.self = self; }; } )).config.result; diff --git a/lib/test/sops.nix b/lib/test/sops.nix new file mode 100644 index 000000000..9ddd4b960 --- /dev/null +++ b/lib/test/sops.nix @@ -0,0 +1,16 @@ +# nixosModule +{ config, lib, ... }: +{ + # configures a static age-key to skip the age-key generation + sops.age.keyFile = "/run/age-key.txt"; + system.activationScripts = + { + setupSecrets.deps = [ "age-key" ]; + age-key.text = '' + echo AGE-SECRET-KEY-1PL0M9CWRCG3PZ9DXRTTLMCVD57U6JDFE8K7DNVQ35F4JENZ6G3MQ0RQLRV > /run/age-key.txt + ''; + } + // lib.optionalAttrs (lib.filterAttrs (_: v: v.neededForUsers) config.sops.secrets != { }) { + setupSecretsForUsers.deps = [ "age-key" ]; + }; +}