tests(clan): move test-inventory.nix to clanLib. And name it 'makeTestClan'

Exposing the function via clanLib makes it more accessible to clan modules
This allows each module to define its own test without needing to depend on any fileSystem path assumptions in the clan-core repo
This commit is contained in:
Johannes Kirschbauer
2025-04-15 18:06:42 +02:00
parent f331295dd5
commit 8ae0f5ddcb
10 changed files with 236 additions and 211 deletions

View File

@@ -1,78 +1,86 @@
(import ../lib/test-inventory.nix) ( {
{ lib, ... }: pkgs,
let self,
clanLib,
...
}:
clanLib.test.makeTestClan {
inherit pkgs self;
nixosTest = (
{ lib, ... }:
let
machines = [
"admin"
"peer"
"signer"
];
in
{
name = "data-mesher";
machines = [ clan = {
"admin" directory = ./.;
"peer" inventory = {
"signer" machines = lib.genAttrs machines (_: { });
]; services = {
in data-mesher.default = {
{ roles.peer.machines = [ "peer" ];
name = "data-mesher"; roles.admin.machines = [ "admin" ];
roles.signer.machines = [ "signer" ];
clan = { };
directory = ./.;
inventory = {
machines = lib.genAttrs machines (_: { });
services = {
data-mesher.default = {
roles.peer.machines = [ "peer" ];
roles.admin.machines = [ "admin" ];
roles.signer.machines = [ "signer" ];
}; };
}; };
}; };
};
defaults = defaults =
{ config, ... }: { config, ... }:
{ {
environment.systemPackages = [ environment.systemPackages = [
config.services.data-mesher.package config.services.data-mesher.package
]; ];
clan.data-mesher.network.interface = "eth1"; clan.data-mesher.network.interface = "eth1";
clan.data-mesher.bootstrapNodes = [ clan.data-mesher.bootstrapNodes = [
"[2001:db8:1::1]:7946" # peer1 "[2001:db8:1::1]:7946" # peer1
"[2001:db8:1::2]:7946" # peer2 "[2001:db8:1::2]:7946" # peer2
]; ];
# speed up for testing # speed up for testing
services.data-mesher.settings = { services.data-mesher.settings = {
cluster.join_interval = lib.mkForce "2s"; cluster.join_interval = lib.mkForce "2s";
cluster.push_pull_interval = lib.mkForce "5s"; cluster.push_pull_interval = lib.mkForce "5s";
};
}; };
nodes = {
admin.clan.data-mesher.network.tld = "foo";
}; };
nodes = { # TODO Add better test script.
admin.clan.data-mesher.network.tld = "foo"; testScript = ''
};
# TODO Add better test script. def resolve(node, success = {}, fail = [], timeout = 60):
testScript = '' for hostname, ips in success.items():
for ip in ips:
node.wait_until_succeeds(f"getent ahosts {hostname} | grep {ip}", timeout)
def resolve(node, success = {}, fail = [], timeout = 60): for hostname in fail:
for hostname, ips in success.items(): node.wait_until_fails(f"getent ahosts {hostname}")
for ip in ips:
node.wait_until_succeeds(f"getent ahosts {hostname} | grep {ip}", timeout)
for hostname in fail: start_all()
node.wait_until_fails(f"getent ahosts {hostname}")
start_all() admin.wait_for_unit("data-mesher")
signer.wait_for_unit("data-mesher")
peer.wait_for_unit("data-mesher")
admin.wait_for_unit("data-mesher") # check dns resolution
signer.wait_for_unit("data-mesher") for node in [admin, signer, peer]:
peer.wait_for_unit("data-mesher") resolve(node, {
"admin.foo": ["2001:db8:1::1", "192.168.1.1"],
# check dns resolution "peer.foo": ["2001:db8:1::2", "192.168.1.2"],
for node in [admin, signer, peer]: "signer.foo": ["2001:db8:1::3", "192.168.1.3"]
resolve(node, { })
"admin.foo": ["2001:db8:1::1", "192.168.1.1"], '';
"peer.foo": ["2001:db8:1::2", "192.168.1.2"], }
"signer.foo": ["2001:db8:1::3", "192.168.1.3"] );
}) }
'';
}
)

View File

@@ -1,67 +1,76 @@
(import ../lib/test-inventory.nix) ( {
{ ... }: pkgs,
{ self,
# This tests the compatibility of the inventory clanLib,
# With the test framework ...
# - legacy-modules }:
# - clan.service modules clanLib.test.makeTestClan {
name = "dummy-inventory-test"; inherit pkgs self;
nixosTest = (
{ ... }:
{
# This tests the compatibility of the inventory
# With the test framework
# - legacy-modules
# - clan.service modules
name = "dummy-inventory-test";
clan = { clan = {
directory = ./.; directory = ./.;
inventory = { inventory = {
machines.peer1 = { }; machines.peer1 = { };
machines.admin1 = { }; machines.admin1 = { };
services = { services = {
legacy-module.default = { legacy-module.default = {
roles.peer.machines = [ "peer1" ]; roles.peer.machines = [ "peer1" ];
roles.admin.machines = [ "admin1" ]; roles.admin.machines = [ "admin1" ];
};
};
instances."test" = {
module.name = "new-service";
roles.peer.machines.peer1 = { };
}; };
};
instances."test" = {
module.name = "new-service";
roles.peer.machines.peer1 = { };
};
modules = { modules = {
legacy-module = ./legacy-module; legacy-module = ./legacy-module;
new-service = { new-service = {
_class = "clan.service"; _class = "clan.service";
manifest.name = "new-service"; manifest.name = "new-service";
roles.peer = { }; roles.peer = { };
perMachine = { perMachine = {
nixosModule = { nixosModule = {
# This should be generated by: # This should be generated by:
# ./pkgs/scripts/update-vars.py inventory-test-framework-compatibility-test # ./pkgs/scripts/update-vars.py inventory-test-framework-compatibility-test
clan.core.vars.generators.new-service = { clan.core.vars.generators.new-service = {
files.hello = { files.hello = {
secret = false; secret = false;
deploy = true; deploy = true;
};
script = ''
# This is a dummy script that does nothing
echo "This is a dummy script" > $out/hello
'';
}; };
script = ''
# This is a dummy script that does nothing
echo "This is a dummy script" > $out/hello
'';
}; };
}; };
}; };
}; };
}; };
}; };
};
testScript = testScript =
{ nodes, ... }: { nodes, ... }:
'' ''
start_all() start_all()
admin1.wait_for_unit("multi-user.target") admin1.wait_for_unit("multi-user.target")
peer1.wait_for_unit("multi-user.target") peer1.wait_for_unit("multi-user.target")
# Provided by the legacy module # Provided by the legacy module
print(admin1.succeed("systemctl status dummy-service")) print(admin1.succeed("systemctl status dummy-service"))
print(peer1.succeed("systemctl status dummy-service")) print(peer1.succeed("systemctl status dummy-service"))
# peer1 should have the 'hello' file # peer1 should have the 'hello' file
peer1.succeed("cat ${nodes.peer1.clan.core.vars.generators.new-service.files.hello.path}") peer1.succeed("cat ${nodes.peer1.clan.core.vars.generators.new-service.files.hello.path}")
''; '';
} }
) );
}

View File

@@ -27,9 +27,10 @@ in
let let
nixosTestArgs = { nixosTestArgs = {
# reference to nixpkgs for the current system # reference to nixpkgs for the current system
inherit pkgs; inherit pkgs lib;
# this gives us a reference to our flake but also all flake inputs # this gives us a reference to our flake but also all flake inputs
inherit self; inherit self;
inherit (self) clanLib;
}; };
nixosTests = lib.optionalAttrs (pkgs.stdenv.isLinux) { nixosTests = lib.optionalAttrs (pkgs.stdenv.isLinux) {
# import our test # import our test

View File

@@ -99,7 +99,7 @@ in
imports = [ imports = [
(modulesPath + "/testing/test-instrumentation.nix") # we need these 2 modules always to be able to run the tests (modulesPath + "/testing/test-instrumentation.nix") # we need these 2 modules always to be able to run the tests
(modulesPath + "/profiles/qemu-guest.nix") (modulesPath + "/profiles/qemu-guest.nix")
../lib/minify.nix self.clanLib.test.minifyModule
]; ];
networking.hostName = "test-install-machine"; networking.hostName = "test-install-machine";

View File

@@ -13,7 +13,7 @@ in
{ config, options, ... }: { config, options, ... }:
{ {
imports = [ imports = [
./minify.nix self.clanLib.test.minifyModule
]; ];
config = lib.mkMerge [ config = lib.mkMerge [
(lib.optionalAttrs (options ? clan) { (lib.optionalAttrs (options ? clan) {

View File

@@ -11,7 +11,7 @@ in
{ config, options, ... }: { config, options, ... }:
{ {
imports = [ imports = [
./minify.nix self.clanLib.test.minifyModule
]; ];
config = lib.mkMerge [ config = lib.mkMerge [
(lib.optionalAttrs (options ? clan) { (lib.optionalAttrs (options ? clan) {

View File

@@ -1,89 +0,0 @@
test:
{ pkgs, self, ... }:
let
inherit (pkgs) lib;
inherit (lib)
mkOption
flip
mapAttrs
types
;
nixos-lib = import (pkgs.path + "/nixos/lib") { };
in
(nixos-lib.runTest (
{ config, ... }:
let
clanFlakeResult = config.clan;
in
{
imports = [ test ];
options = {
clanSettings = mkOption {
default = { };
type = types.submodule {
options = {
clan-core = mkOption { default = self; };
nixpkgs = mkOption { default = self.inputs.nixpkgs; };
nix-darwin = mkOption { default = self.inputs.nix-darwin; };
};
};
};
clan = mkOption {
default = { };
type = types.submoduleWith {
specialArgs = {
inherit (config.clanSettings)
clan-core
nixpkgs
nix-darwin
;
};
modules = [
self.clanLib.buildClanModule.flakePartsModule
];
};
};
};
config = {
nodes = flip mapAttrs clanFlakeResult.clanInternals.inventoryClass.machines (
machineName: attrs: {
imports = attrs.machineImports ++ [ self.nixosModules.clanCore ];
clan.core.settings.directory = "${config.clan.directory}";
clan.core.settings.machine.name = machineName;
}
);
hostPkgs = pkgs;
# speed-up evaluation
defaults = (
{ config, ... }:
{
imports = [
./minify.nix
];
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" ];
};
}
);
_module.args = { inherit self; };
# to accept external dependencies such as disko
node.specialArgs.self = self;
};
}
)).config.result

View File

@@ -25,6 +25,7 @@ lib.fix (clanLib: {
buildClanModule = clanLib.callLib ./build-clan { }; buildClanModule = clanLib.callLib ./build-clan { };
inventory = clanLib.callLib ./inventory { }; inventory = clanLib.callLib ./inventory { };
modules = clanLib.callLib ./inventory/frontmatter { }; modules = clanLib.callLib ./inventory/frontmatter { };
test = clanLib.callLib ./tests { };
# Plain imports. # Plain imports.
values = import ./introspection { inherit lib; }; values = import ./introspection { inherit lib; };

92
lib/tests/default.nix Normal file
View File

@@ -0,0 +1,92 @@
{ lib, clanLib }:
let
inherit (lib)
mkOption
types
;
in
{
minifyModule = ./minify.nix;
# A function that returns an extension to runTest
makeTestClan =
{
nixosTest,
pkgs,
self,
...
}:
let
nixos-lib = import (pkgs.path + "/nixos/lib") { };
in
(nixos-lib.runTest (
{ config, ... }:
let
clanFlakeResult = config.clan;
in
{
imports = [ nixosTest ];
options = {
clanSettings = mkOption {
default = { };
type = types.submodule {
options = {
clan-core = mkOption { default = self; };
nixpkgs = mkOption { default = self.inputs.nixpkgs; };
nix-darwin = mkOption { default = self.inputs.nix-darwin; };
};
};
};
clan = mkOption {
default = { };
type = types.submoduleWith {
specialArgs = {
inherit (config.clanSettings)
clan-core
nixpkgs
nix-darwin
;
};
modules = [
clanLib.buildClanModule.flakePartsModule
];
};
};
};
config = {
nodes = clanFlakeResult.clanInternals.nixosModules;
hostPkgs = pkgs;
# speed-up evaluation
defaults = (
{ config, ... }:
{
imports = [
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" ];
};
}
);
# to accept external dependencies such as disko
_module.args = { inherit self; };
node.specialArgs.self = self;
};
}
)).config.result;
}

View File

@@ -1,3 +1,6 @@
# This is a module to reduce the size of the NixOS configuration
# Used by the tests
# It unsets some unnecessary options
{ lib, ... }: { lib, ... }:
{ {
nixpkgs.flake.setFlakeRegistry = false; nixpkgs.flake.setFlakeRegistry = false;