diff --git a/checks/admin/default.nix b/checks/admin/default.nix index 9d2527e5e..e56cb2084 100644 --- a/checks/admin/default.nix +++ b/checks/admin/default.nix @@ -1,61 +1,62 @@ { pkgs, - self, - clanLib, + nixosLib, + clan-core, ... }: let public-key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAII6zj7ubTg6z/aDwRNwvM/WlQdUocMprQ8E92NWxl6t+ test@test"; in +nixosLib.runTest ( + { ... }: + { + imports = [ + clan-core.modules.nixosVmTest.clanTest + ]; -clanLib.test.makeTestClan { - inherit pkgs self; - nixosTest = ( - { ... }: + hostPkgs = pkgs; - { - name = "admin"; + name = "admin"; - clan = { - directory = ./.; - modules."@clan/admin" = ../../clanServices/admin/default.nix; - inventory = { + clan = { + directory = ./.; + modules."@clan/admin" = ../../clanServices/admin/default.nix; + inventory = { - machines.client = { }; - machines.server = { }; + machines.client = { }; + machines.server = { }; - instances = { - ssh-test-one = { - module.name = "@clan/admin"; - roles.default.machines."server".settings = { - allowedKeys.testkey = public-key; - }; + instances = { + ssh-test-one = { + module.name = "@clan/admin"; + roles.default.machines."server".settings = { + allowedKeys.testkey = public-key; }; }; }; }; + }; - nodes = { - client.environment.etc.private-test-key.source = ./private-test-key; + nodes = { + client.environment.etc.private-test-key.source = ./private-test-key; - server = { - services.openssh.enable = true; - }; + server = { + services.openssh.enable = true; }; + }; - testScript = '' - start_all() + testScript = '' + start_all() - machines = [client, server] - for m in machines: - m.systemctl("start network-online.target") + machines = [client, server] + for m in machines: + m.systemctl("start network-online.target") - for m in machines: - m.wait_for_unit("network-online.target") + for m in machines: + m.wait_for_unit("network-online.target") - client.succeed(f"ssh -F /dev/null -i /etc/private-test-key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o BatchMode=yes root@server true &>/dev/null") - ''; - } - ); -} + client.succeed(f"ssh -F /dev/null -i /etc/private-test-key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o BatchMode=yes root@server true &>/dev/null") + ''; + } +) diff --git a/checks/borgbackup/default.nix b/checks/borgbackup/default.nix index e0cb8a890..90b59f882 100644 --- a/checks/borgbackup/default.nix +++ b/checks/borgbackup/default.nix @@ -1,118 +1,118 @@ { pkgs, - self, - clanLib, + nixosLib, + clan-core, ... }: +nixosLib.runTest ( + { ... }: + { + imports = [ + clan-core.modules.nixosVmTest.clanTest + ]; -clanLib.test.makeTestClan { - inherit pkgs self; - useContainers = true; + hostPkgs = pkgs; - nixosTest = ( - { ... }: + name = "borgbackup"; - { - name = "borgbackup"; + clan = { + directory = ./.; + test.useContainers = true; + modules."@clan/borgbackup" = ../../clanServices/borgbackup/default.nix; + inventory = { - clan = { - directory = ./.; - modules."@clan/borgbackup" = ../../clanServices/borgbackup/default.nix; - inventory = { + machines.clientone = { }; + machines.serverone = { }; - machines.clientone = { }; - machines.serverone = { }; + instances = { + borgone = { - instances = { - borgone = { + module.name = "@clan/borgbackup"; - module.name = "@clan/borgbackup"; - - roles.client.machines."clientone" = { }; - roles.server.machines."serverone".settings.directory = "/tmp/borg-test"; - }; + roles.client.machines."clientone" = { }; + roles.server.machines."serverone".settings.directory = "/tmp/borg-test"; }; }; }; + }; - nodes = { + nodes = { + + serverone = { + services.openssh.enable = true; + # Needed so PAM doesn't see the user as locked + users.users.borg.password = "borg"; + }; + + clientone = + { config, pkgs, ... }: + let + dependencies = [ + clan-core + pkgs.stdenv.drvPath + ] ++ builtins.map (i: i.outPath) (builtins.attrValues clan-core.inputs); + closureInfo = pkgs.closureInfo { rootPaths = dependencies; }; + + in + { - serverone = { services.openssh.enable = true; - # Needed so PAM doesn't see the user as locked - users.users.borg.password = "borg"; + + users.users.root.openssh.authorizedKeys.keyFiles = [ ../assets/ssh/pubkey ]; + + clan.core.networking.targetHost = config.networking.hostName; + + environment.systemPackages = [ clan-core.packages.${pkgs.system}.clan-cli ]; + + environment.etc.install-closure.source = "${closureInfo}/store-paths"; + nix.settings = { + substituters = pkgs.lib.mkForce [ ]; + hashed-mirrors = null; + connect-timeout = pkgs.lib.mkForce 3; + flake-registry = pkgs.writeText "flake-registry" ''{"flakes":[],"version":2}''; + }; + system.extraDependencies = dependencies; + + clan.core.state.test-backups.folders = [ "/var/test-backups" ]; }; - clientone = - { config, pkgs, ... }: - let - dependencies = [ - self - pkgs.stdenv.drvPath - ] ++ builtins.map (i: i.outPath) (builtins.attrValues self.inputs); - closureInfo = pkgs.closureInfo { rootPaths = dependencies; }; + }; - in - { + testScript = '' + import json + start_all() - services.openssh.enable = true; + machines = [clientone, serverone] - users.users.root.openssh.authorizedKeys.keyFiles = [ ../assets/ssh/pubkey ]; + for m in machines: + m.systemctl("start network-online.target") - clan.core.networking.targetHost = config.networking.hostName; + for m in machines: + m.wait_for_unit("network-online.target") - environment.systemPackages = [ self.packages.${pkgs.system}.clan-cli ]; + # dummy data + clientone.succeed("mkdir -p /var/test-backups /var/test-service") + clientone.succeed("echo testing > /var/test-backups/somefile") - environment.etc.install-closure.source = "${closureInfo}/store-paths"; - nix.settings = { - substituters = pkgs.lib.mkForce [ ]; - hashed-mirrors = null; - connect-timeout = pkgs.lib.mkForce 3; - flake-registry = pkgs.writeText "flake-registry" ''{"flakes":[],"version":2}''; - }; - system.extraDependencies = dependencies; + clientone.succeed("${pkgs.coreutils}/bin/install -Dm 600 ${../assets/ssh/privkey} /root/.ssh/id_ed25519") + clientone.succeed("${pkgs.coreutils}/bin/touch /root/.ssh/known_hosts") + clientone.wait_until_succeeds("timeout 2 ssh -o StrictHostKeyChecking=accept-new localhost hostname") + clientone.wait_until_succeeds("timeout 2 ssh -o StrictHostKeyChecking=accept-new $(hostname) hostname") - clan.core.state.test-backups.folders = [ "/var/test-backups" ]; - }; + # create + clientone.succeed("borgbackup-create >&2") + clientone.wait_until_succeeds("! systemctl is-active borgbackup-job-serverone >&2") - }; + # list + backup_id = json.loads(clientone.succeed("borg-job-serverone list --json"))["archives"][0]["archive"] + out = clientone.succeed("borgbackup-list").strip() + print(out) + assert backup_id in out, f"backup {backup_id} not found in {out}" - testScript = '' - import json - start_all() - - machines = [clientone, serverone] - - for m in machines: - m.systemctl("start network-online.target") - - for m in machines: - m.wait_for_unit("network-online.target") - - # dummy data - clientone.succeed("mkdir -p /var/test-backups /var/test-service") - clientone.succeed("echo testing > /var/test-backups/somefile") - - clientone.succeed("${pkgs.coreutils}/bin/install -Dm 600 ${../assets/ssh/privkey} /root/.ssh/id_ed25519") - clientone.succeed("${pkgs.coreutils}/bin/touch /root/.ssh/known_hosts") - clientone.wait_until_succeeds("timeout 2 ssh -o StrictHostKeyChecking=accept-new localhost hostname") - clientone.wait_until_succeeds("timeout 2 ssh -o StrictHostKeyChecking=accept-new $(hostname) hostname") - - # create - clientone.succeed("borgbackup-create >&2") - clientone.wait_until_succeeds("! systemctl is-active borgbackup-job-serverone >&2") - - # list - backup_id = json.loads(clientone.succeed("borg-job-serverone list --json"))["archives"][0]["archive"] - out = clientone.succeed("borgbackup-list").strip() - print(out) - assert backup_id in out, f"backup {backup_id} not found in {out}" - - # borgbackup restore - clientone.succeed("rm -f /var/test-backups/somefile") - clientone.succeed(f"NAME='serverone::borg@serverone:.::{backup_id}' borgbackup-restore >&2") - assert clientone.succeed("cat /var/test-backups/somefile").strip() == "testing", "restore failed" - ''; - } - ); -} + # borgbackup restore + clientone.succeed("rm -f /var/test-backups/somefile") + clientone.succeed(f"NAME='serverone::borg@serverone:.::{backup_id}' borgbackup-restore >&2") + assert clientone.succeed("cat /var/test-backups/somefile").strip() == "testing", "restore failed" + ''; + } +) diff --git a/checks/data-mesher/default.nix b/checks/data-mesher/default.nix index 653cc266a..a79bfe98e 100644 --- a/checks/data-mesher/default.nix +++ b/checks/data-mesher/default.nix @@ -1,86 +1,89 @@ { pkgs, - self, - clanLib, + nixosLib, + clan-core, + lib, ... }: -clanLib.test.makeTestClan { - inherit pkgs self; - nixosTest = ( - { lib, ... }: - let - machines = [ - "admin" - "peer" - "signer" - ]; - in - { - name = "data-mesher"; +let + machines = [ + "admin" + "peer" + "signer" + ]; +in +nixosLib.runTest ( + { ... }: + { + imports = [ + clan-core.modules.nixosVmTest.clanTest + ]; - clan = { - directory = ./.; - inventory = { - machines = lib.genAttrs machines (_: { }); - services = { - data-mesher.default = { - roles.peer.machines = [ "peer" ]; - roles.admin.machines = [ "admin" ]; - roles.signer.machines = [ "signer" ]; - }; + hostPkgs = pkgs; + name = "data-mesher"; + + clan = { + directory = ./.; + inventory = { + machines = lib.genAttrs machines (_: { }); + services = { + data-mesher.default = { + roles.peer.machines = [ "peer" ]; + roles.admin.machines = [ "admin" ]; + roles.signer.machines = [ "signer" ]; }; }; }; + }; - defaults = - { config, ... }: - { - environment.systemPackages = [ - config.services.data-mesher.package - ]; + defaults = + { config, ... }: + { + environment.systemPackages = [ + config.services.data-mesher.package + ]; - clan.data-mesher.network.interface = "eth1"; - clan.data-mesher.bootstrapNodes = [ - "[2001:db8:1::1]:7946" # peer1 - "[2001:db8:1::2]:7946" # peer2 - ]; + clan.data-mesher.network.interface = "eth1"; + clan.data-mesher.bootstrapNodes = [ + "[2001:db8:1::1]:7946" # peer1 + "[2001:db8:1::2]:7946" # peer2 + ]; - # speed up for testing - services.data-mesher.settings = { - cluster.join_interval = lib.mkForce "2s"; - cluster.push_pull_interval = lib.mkForce "5s"; - }; + # speed up for testing + services.data-mesher.settings = { + cluster.join_interval = lib.mkForce "2s"; + cluster.push_pull_interval = lib.mkForce "5s"; }; - - nodes = { - admin.clan.data-mesher.network.tld = "foo"; }; - # TODO Add better test script. - testScript = '' + nodes = { + admin.clan.data-mesher.network.tld = "foo"; + }; - def resolve(node, success = {}, fail = [], timeout = 60): - for hostname, ips in success.items(): - for ip in ips: - node.wait_until_succeeds(f"getent ahosts {hostname} | grep {ip}", timeout) + # TODO Add better test script. + testScript = '' - for hostname in fail: - node.wait_until_fails(f"getent ahosts {hostname}") + def resolve(node, success = {}, fail = [], timeout = 60): + for hostname, ips in success.items(): + for ip in ips: + node.wait_until_succeeds(f"getent ahosts {hostname} | grep {ip}", timeout) - start_all() + for hostname in fail: + node.wait_until_fails(f"getent ahosts {hostname}") - admin.wait_for_unit("data-mesher") - signer.wait_for_unit("data-mesher") - peer.wait_for_unit("data-mesher") + start_all() - # check dns resolution - for node in [admin, signer, peer]: - 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"] - }) - ''; - } - ); -} + admin.wait_for_unit("data-mesher") + signer.wait_for_unit("data-mesher") + peer.wait_for_unit("data-mesher") + + # check dns resolution + for node in [admin, signer, peer]: + 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"] + }) + ''; + } +) diff --git a/checks/dummy-inventory-test/default.nix b/checks/dummy-inventory-test/default.nix index 6cebf5096..9bbfe3a21 100644 --- a/checks/dummy-inventory-test/default.nix +++ b/checks/dummy-inventory-test/default.nix @@ -1,93 +1,96 @@ { pkgs, - self, - clanLib, + nixosLib, + clan-core, ... }: -clanLib.test.makeTestClan { - inherit pkgs self; - nixosTest = ( - { ... }: - { - # This tests the compatibility of the inventory - # With the test framework - # - legacy-modules - # - clan.service modules - name = "dummy-inventory-test"; +nixosLib.runTest ( + { ... }: + { + imports = [ + clan-core.modules.nixosVmTest.clanTest + ]; - clan = { - directory = ./.; - inventory = { - machines.peer1 = { }; - machines.admin1 = { }; - services = { - legacy-module.default = { - roles.peer.machines = [ "peer1" ]; - roles.admin.machines = [ "admin1" ]; - }; - }; + hostPkgs = pkgs; - instances."test" = { - module.name = "new-service"; - roles.peer.machines.peer1 = { }; - }; + # This tests the compatibility of the inventory + # With the test framework + # - legacy-modules + # - clan.service modules + name = "dummy-inventory-test"; - modules = { - legacy-module = ./legacy-module; + clan = { + directory = ./.; + inventory = { + machines.peer1 = { }; + machines.admin1 = { }; + services = { + legacy-module.default = { + roles.peer.machines = [ "peer1" ]; + roles.admin.machines = [ "admin1" ]; }; }; - modules.new-service = { - _class = "clan.service"; - manifest.name = "new-service"; - roles.peer = { }; - perMachine = { - nixosModule = { - # This should be generated by: - # nix run .#generate-test-vars -- checks/dummy-inventory-test dummy-inventory-test - clan.core.vars.generators.new-service = { - files.not-a-secret = { - secret = false; - deploy = true; - }; - files.a-secret = { - secret = true; - deploy = true; - owner = "nobody"; - group = "users"; - mode = "0644"; - }; - script = '' - # This is a dummy script that does nothing - echo -n "not-a-secret" > $out/not-a-secret - echo -n "a-secret" > $out/a-secret - ''; + + instances."test" = { + module.name = "new-service"; + roles.peer.machines.peer1 = { }; + }; + + modules = { + legacy-module = ./legacy-module; + }; + }; + modules.new-service = { + _class = "clan.service"; + manifest.name = "new-service"; + roles.peer = { }; + perMachine = { + nixosModule = { + # This should be generated by: + # nix run .#generate-test-vars -- checks/dummy-inventory-test dummy-inventory-test + clan.core.vars.generators.new-service = { + files.not-a-secret = { + secret = false; + deploy = true; }; + files.a-secret = { + secret = true; + deploy = true; + owner = "nobody"; + group = "users"; + mode = "0644"; + }; + script = '' + # This is a dummy script that does nothing + echo -n "not-a-secret" > $out/not-a-secret + echo -n "a-secret" > $out/a-secret + ''; }; }; }; }; + }; - testScript = - { nodes, ... }: - '' - start_all() - admin1.wait_for_unit("multi-user.target") - peer1.wait_for_unit("multi-user.target") - # Provided by the legacy module - print(admin1.succeed("systemctl status dummy-service")) - print(peer1.succeed("systemctl status dummy-service")) + testScript = + { nodes, ... }: + '' + start_all() + admin1.wait_for_unit("multi-user.target") + peer1.wait_for_unit("multi-user.target") + # Provided by the legacy module + print(admin1.succeed("systemctl status dummy-service")) + print(peer1.succeed("systemctl status dummy-service")) - # peer1 should have the 'hello' file - peer1.succeed("cat ${nodes.peer1.clan.core.vars.generators.new-service.files.not-a-secret.path}") + # peer1 should have the 'hello' file + peer1.succeed("cat ${nodes.peer1.clan.core.vars.generators.new-service.files.not-a-secret.path}") - ls_out = peer1.succeed("ls -la ${nodes.peer1.clan.core.vars.generators.new-service.files.a-secret.path}") - # Check that the file is owned by 'nobody' - assert "nobody" in ls_out, f"File is not owned by 'nobody': {ls_out}" - # Check that the file is in the 'users' group - assert "users" in ls_out, f"File is not in the 'users' group: {ls_out}" - # Check that the file is in the '0644' mode - assert "-rw-r--r--" in ls_out, f"File is not in the '0644' mode: {ls_out}" - ''; - } - ); -} + ls_out = peer1.succeed("ls -la ${nodes.peer1.clan.core.vars.generators.new-service.files.a-secret.path}") + # Check that the file is owned by 'nobody' + assert "nobody" in ls_out, f"File is not owned by 'nobody': {ls_out}" + # Check that the file is in the 'users' group + assert "users" in ls_out, f"File is not in the 'users' group: {ls_out}" + # Check that the file is in the '0644' mode + assert "-rw-r--r--" in ls_out, f"File is not in the '0644' mode: {ls_out}" + ''; + } +) diff --git a/checks/flake-module.nix b/checks/flake-module.nix index bbf55cd3e..622cbf60f 100644 --- a/checks/flake-module.nix +++ b/checks/flake-module.nix @@ -4,6 +4,7 @@ let filter pathExists ; + nixosLib = import (self.inputs.nixpkgs + "/nixos/lib") { }; in { imports = filter pathExists [ @@ -29,10 +30,11 @@ in let nixosTestArgs = { # reference to nixpkgs for the current system - inherit pkgs lib; + inherit pkgs lib nixosLib; # this gives us a reference to our flake but also all flake inputs inherit self; inherit (self) clanLib; + clan-core = self; }; nixosTests = lib.optionalAttrs (pkgs.stdenv.isLinux) { diff --git a/checks/mumble/default.nix b/checks/mumble/default.nix index a248a1079..a73fc87ce 100644 --- a/checks/mumble/default.nix +++ b/checks/mumble/default.nix @@ -1,130 +1,132 @@ { pkgs, - self, - clanLib, + nixosLib, + clan-core, + lib, ... }: -clanLib.test.makeTestClan { - inherit pkgs self; - # TODO: container driver does not support: sleep, wait_for_window, send_chars, wait_for_text - useContainers = false; - nixosTest = ( - { lib, ... }: - let - common = - { pkgs, modulesPath, ... }: - { - imports = [ - (modulesPath + "/../tests/common/x11.nix") - ]; +nixosLib.runTest ( + { ... }: + let + machines = [ + "peer1" + "peer2" + ]; + in + { + imports = [ + clan-core.modules.nixosVmTest.clanTest + ]; - clan.services.mumble.user = "alice"; - environment.systemPackages = [ pkgs.killall ]; - }; - machines = [ - "peer1" - "peer2" - ]; - in - { - name = "mumble"; + hostPkgs = pkgs; - clan = { - directory = ./.; - inventory = { - machines = lib.genAttrs machines (_: { }); - services = { - mumble.default = { - roles.server.machines = machines; - }; + name = "mumble"; + + defaults = + { pkgs, modulesPath, ... }: + { + imports = [ + (modulesPath + "/../tests/common/x11.nix") + ]; + + clan.services.mumble.user = "alice"; + environment.systemPackages = [ pkgs.killall ]; + }; + + clan = { + directory = ./.; + # TODO: container driver does not support: sleep, wait_for_window, send_chars, wait_for_text + test.useContainers = false; + inventory = { + machines = lib.genAttrs machines (_: { }); + services = { + mumble.default = { + roles.server.machines = machines; }; }; }; + }; - enableOCR = true; + enableOCR = true; - nodes.peer1 = common; - nodes.peer2 = common; - - testScript = '' - import time - import re + testScript = '' + import time + import re - def machine_has_text(machine: Machine, regex: str) -> bool: - variants = machine.get_screen_text_variants() - # for debugging - # machine.screenshot(f"/tmp/{machine.name}.png") - for text in variants: - print(f"Expecting '{regex}' in '{text}'") - if re.search(regex, text) is not None: - return True - return False + def machine_has_text(machine: Machine, regex: str) -> bool: + variants = machine.get_screen_text_variants() + # for debugging + # machine.screenshot(f"/tmp/{machine.name}.png") + for text in variants: + print(f"Expecting '{regex}' in '{text}'") + if re.search(regex, text) is not None: + return True + return False - start_all() + start_all() - with subtest("Waiting for x"): - peer1.wait_for_x() - peer2.wait_for_x() + with subtest("Waiting for x"): + peer1.wait_for_x() + peer2.wait_for_x() - with subtest("Waiting for murmur"): - peer1.wait_for_unit("murmur.service") - peer2.wait_for_unit("murmur.service") + with subtest("Waiting for murmur"): + peer1.wait_for_unit("murmur.service") + peer2.wait_for_unit("murmur.service") - with subtest("Starting Mumble"): - # starting mumble is blocking - peer1.execute("mumble >&2 &") - peer2.execute("mumble >&2 &") + with subtest("Starting Mumble"): + # starting mumble is blocking + peer1.execute("mumble >&2 &") + peer2.execute("mumble >&2 &") - with subtest("Wait for Mumble"): - peer1.wait_for_window(r"Mumble") - peer2.wait_for_window(r"Mumble") + with subtest("Wait for Mumble"): + peer1.wait_for_window(r"Mumble") + peer2.wait_for_window(r"Mumble") - with subtest("Wait for certificate creation"): - peer1.wait_for_window(r"Mumble") - peer2.wait_for_window(r"Mumble") + with subtest("Wait for certificate creation"): + peer1.wait_for_window(r"Mumble") + peer2.wait_for_window(r"Mumble") - for i in range(20): - time.sleep(1) - peer1.send_chars("\n") - peer1.send_chars("\n") - peer2.send_chars("\n") - peer2.send_chars("\n") - if machine_has_text(peer1, r"Mumble Server Connect") and \ - machine_has_text(peer2, r"Mumble Server Connect"): - break - else: - raise Exception("Timeout waiting for certificate creation") + for i in range(20): + time.sleep(1) + peer1.send_chars("\n") + peer1.send_chars("\n") + peer2.send_chars("\n") + peer2.send_chars("\n") + if machine_has_text(peer1, r"Mumble Server Connect") and \ + machine_has_text(peer2, r"Mumble Server Connect"): + break + else: + raise Exception("Timeout waiting for certificate creation") - with subtest("Check validity of server certificates"): - peer1.execute("killall .mumble-wrapped") - peer1.sleep(1) - peer1.execute("mumble mumble://peer2 >&2 &") - peer1.wait_for_window(r"Mumble") + with subtest("Check validity of server certificates"): + peer1.execute("killall .mumble-wrapped") + peer1.sleep(1) + peer1.execute("mumble mumble://peer2 >&2 &") + peer1.wait_for_window(r"Mumble") - for i in range(20): - time.sleep(1) - peer1.send_chars("\n") - peer1.send_chars("\n") - if machine_has_text(peer1, "Connected."): - break - else: - raise Exception("Timeout waiting for certificate creation") + for i in range(20): + time.sleep(1) + peer1.send_chars("\n") + peer1.send_chars("\n") + if machine_has_text(peer1, "Connected."): + break + else: + raise Exception("Timeout waiting for certificate creation") - peer2.execute("killall .mumble-wrapped") - peer2.sleep(1) - peer2.execute("mumble mumble://peer1 >&2 &") - peer2.wait_for_window(r"Mumble") + peer2.execute("killall .mumble-wrapped") + peer2.sleep(1) + peer2.execute("mumble mumble://peer1 >&2 &") + peer2.wait_for_window(r"Mumble") - for i in range(20): - time.sleep(1) - peer2.send_chars("\n") - peer2.send_chars("\n") - if machine_has_text(peer2, "Connected."): - break - else: - raise Exception("Timeout waiting for certificate creation") - ''; - } - ); -} + for i in range(20): + time.sleep(1) + peer2.send_chars("\n") + peer2.send_chars("\n") + if machine_has_text(peer2, "Connected."): + break + else: + raise Exception("Timeout waiting for certificate creation") + ''; + } +) diff --git a/checks/syncthing/default.nix b/checks/syncthing/default.nix index d1730cae4..3bb13167f 100644 --- a/checks/syncthing/default.nix +++ b/checks/syncthing/default.nix @@ -1,83 +1,87 @@ { pkgs, - self, - clanLib, + nixosLib, + clan-core, + lib, ... }: -clanLib.test.makeTestClan { - inherit pkgs self; - # TODO: container driver does not support wait_for_file() yet - useContainers = false; - nixosTest = ( - { lib, ... }: - { - name = "syncthing"; +nixosLib.runTest ( + { ... }: + { + imports = [ + clan-core.modules.nixosVmTest.clanTest + ]; - clan = { - directory = ./.; - inventory = { - machines = lib.genAttrs [ - "introducer" - "peer1" - "peer2" - ] (_: { }); - services = { - syncthing.default = { - roles.peer.machines = [ - "peer1" - "peer2" - ]; - roles.introducer.machines = [ "introducer" ]; + hostPkgs = pkgs; + + name = "syncthing"; + + clan = { + directory = ./.; + # TODO: container driver does not support wait_for_file() yet + test.useContainers = false; + inventory = { + machines = lib.genAttrs [ + "introducer" + "peer1" + "peer2" + ] (_: { }); + services = { + syncthing.default = { + roles.peer.machines = [ + "peer1" + "peer2" + ]; + roles.introducer.machines = [ "introducer" ]; + }; + }; + }; + }; + + nodes.introducer = { + # Doesn't test zerotier! + services.syncthing.openDefaultPorts = true; + services.syncthing.settings.folders = { + "Shared" = { + enable = true; + path = "~/Shared"; + versioning = { + type = "trashcan"; + params = { + cleanoutDays = "30"; }; }; }; }; + clan.syncthing.autoAcceptDevices = true; + clan.syncthing.autoShares = [ "Shared" ]; + # For faster Tests + systemd.timers.syncthing-auto-accept.timerConfig = { + OnActiveSec = 1; + OnUnitActiveSec = 1; + }; + }; + nodes.peer1 = { + services.syncthing.openDefaultPorts = true; + }; + nodes.peer2 = { + services.syncthing.openDefaultPorts = true; + }; - nodes.introducer = { - # Doesn't test zerotier! - services.syncthing.openDefaultPorts = true; - services.syncthing.settings.folders = { - "Shared" = { - enable = true; - path = "~/Shared"; - versioning = { - type = "trashcan"; - params = { - cleanoutDays = "30"; - }; - }; - }; - }; - clan.syncthing.autoAcceptDevices = true; - clan.syncthing.autoShares = [ "Shared" ]; - # For faster Tests - systemd.timers.syncthing-auto-accept.timerConfig = { - OnActiveSec = 1; - OnUnitActiveSec = 1; - }; - }; - nodes.peer1 = { - services.syncthing.openDefaultPorts = true; - }; - nodes.peer2 = { - services.syncthing.openDefaultPorts = true; - }; - - testScript = '' - start_all() - introducer.wait_for_unit("syncthing") - peer1.wait_for_unit("syncthing") - peer2.wait_for_unit("syncthing") - peer1.execute("ls -la /var/lib/syncthing") - peer2.execute("ls -la /var/lib/syncthing") - peer1.wait_for_file("/var/lib/syncthing/Shared") - peer2.wait_for_file("/var/lib/syncthing/Shared") - introducer.shutdown() - peer1.execute("echo hello > /var/lib/syncthing/Shared/hello") - peer2.wait_for_file("/var/lib/syncthing/Shared/hello") - out = peer2.succeed("cat /var/lib/syncthing/Shared/hello") - assert "hello" in out - ''; - } - ); -} + testScript = '' + start_all() + introducer.wait_for_unit("syncthing") + peer1.wait_for_unit("syncthing") + peer2.wait_for_unit("syncthing") + peer1.execute("ls -la /var/lib/syncthing") + peer2.execute("ls -la /var/lib/syncthing") + peer1.wait_for_file("/var/lib/syncthing/Shared") + peer2.wait_for_file("/var/lib/syncthing/Shared") + introducer.shutdown() + peer1.execute("echo hello > /var/lib/syncthing/Shared/hello") + peer2.wait_for_file("/var/lib/syncthing/Shared/hello") + out = peer2.succeed("cat /var/lib/syncthing/Shared/hello") + assert "hello" in out + ''; + } +) diff --git a/clanServices/hello-world/flake-module.nix b/clanServices/hello-world/flake-module.nix index 91c9943e7..b3d8d6394 100644 --- a/clanServices/hello-world/flake-module.nix +++ b/clanServices/hello-world/flake-module.nix @@ -47,8 +47,8 @@ in hello-service = import ./tests/vm/default.nix { inherit module; inherit self inputs pkgs; - # clanLib is exposed from inputs.clan-core - clanLib = self.clanLib; + nixosLib = import (self.inputs.nixpkgs + "/nixos/lib") { }; + clan-core = self; }; }; }; diff --git a/clanServices/hello-world/tests/vm/default.nix b/clanServices/hello-world/tests/vm/default.nix index 6cc8d3b4e..bddf8a808 100644 --- a/clanServices/hello-world/tests/vm/default.nix +++ b/clanServices/hello-world/tests/vm/default.nix @@ -1,41 +1,44 @@ { pkgs, - self, - clanLib, + nixosLib, + clan-core, module, ... }: -clanLib.test.makeTestClan { - inherit pkgs self; - nixosTest = ( - { ... }: - { - name = "hello-service"; +nixosLib.runTest ( + { ... }: + { + imports = [ + clan-core.modules.nixosVmTest.clanTest + ]; - clan = { - directory = ./.; - modules = { - hello-service = module; - }; - inventory = { - machines.peer1 = { }; + hostPkgs = pkgs; - instances."test" = { - module.name = "hello-service"; - roles.peer.machines.peer1 = { }; - }; + name = "hello-service"; + + clan = { + directory = ./.; + modules = { + hello-service = module; + }; + inventory = { + machines.peer1 = { }; + + instances."test" = { + module.name = "hello-service"; + roles.peer.machines.peer1 = { }; }; }; + }; - testScript = - { nodes, ... }: - '' - start_all() + testScript = + { nodes, ... }: + '' + start_all() - # peer1 should have the 'hello' file - value = peer1.succeed("cat ${nodes.peer1.clan.core.vars.generators.hello.files.hello.path}") - assert value.strip() == "Hello world from peer1", value - ''; - } - ); -} + # peer1 should have the 'hello' file + value = peer1.succeed("cat ${nodes.peer1.clan.core.vars.generators.hello.files.hello.path}") + assert value.strip() == "Hello world from peer1", value + ''; + } +) diff --git a/clanServices/wifi/flake-module.nix b/clanServices/wifi/flake-module.nix index 63c69ff36..525b9a2e7 100644 --- a/clanServices/wifi/flake-module.nix +++ b/clanServices/wifi/flake-module.nix @@ -28,8 +28,9 @@ in lib.optionalAttrs (pkgs.stdenv.isLinux) { wifi-service = import ./tests/vm/default.nix { inherit module; - inherit self inputs pkgs; - clanLib = self.clanLib; + inherit inputs pkgs; + clan-core = self; + nixosLib = import (self.inputs.nixpkgs + "/nixos/lib") { }; }; }; }; diff --git a/clanServices/wifi/tests/vm/default.nix b/clanServices/wifi/tests/vm/default.nix index 03998a85d..dec7743e0 100644 --- a/clanServices/wifi/tests/vm/default.nix +++ b/clanServices/wifi/tests/vm/default.nix @@ -1,43 +1,46 @@ { pkgs, - self, - clanLib, + nixosLib, + clan-core, module, ... }: -clanLib.test.makeTestClan { - inherit pkgs self; - useContainers = false; - nixosTest = ( - { ... }: - { - name = "wifi-service"; +nixosLib.runTest ( + { ... }: + { + imports = [ + clan-core.modules.nixosVmTest.clanTest + ]; - clan = { - directory = ./.; - modules."@clan/wifi" = module; - inventory = { + hostPkgs = pkgs; - machines.test = { }; + name = "wifi-service"; - instances = { - wg-test-one = { - module.name = "@clan/wifi"; + clan = { + directory = ./.; + test.useContainers = false; + modules."@clan/wifi" = module; + inventory = { - roles.default.machines = { - test.settings.networks.one = { }; - }; + machines.test = { }; + + instances = { + wg-test-one = { + module.name = "@clan/wifi"; + + roles.default.machines = { + test.settings.networks.one = { }; }; }; }; }; + }; - testScript = '' - start_all() - test.wait_for_unit("NetworkManager.service") - psk = test.succeed("cat /run/NetworkManager/system-connections/one.nmconnection") - assert "password-eins" in psk, "Password is incorrect" - ''; - } - ); -} + testScript = '' + start_all() + test.wait_for_unit("NetworkManager.service") + psk = test.succeed("cat /run/NetworkManager/system-connections/one.nmconnection") + assert "password-eins" in psk, "Password is incorrect" + ''; + } +) diff --git a/lib/clanTest/flake-module.nix b/lib/clanTest/flake-module.nix new file mode 100644 index 000000000..11f784fbf --- /dev/null +++ b/lib/clanTest/flake-module.nix @@ -0,0 +1,197 @@ +{ + lib, + self, + config, + ... +}: +let + inherit (lib) + mkOption + removePrefix + types + mapAttrsToList + flip + unique + flatten + ; + + clanLib = config.flake.clanLib; +in +{ + # A function that returns an extension to runTest + # TODO: remove this from clanLib, add to legacyPackages, simplify signature + flake.modules.nixosVmTest.clanTest = + { config, hostPkgs, ... }: + let + clanFlakeResult = config.clan; + testName = config.name; + + update-vars-script = "${ + self.packages.${hostPkgs.system}.generate-test-vars + }/bin/generate-test-vars"; + + relativeDir = removePrefix ("${self}/") (toString config.clan.directory); + + update-vars = hostPkgs.writeShellScriptBin "update-vars" '' + ${update-vars-script} $PRJ_ROOT/${relativeDir} ${testName} + ''; + + testSrc = lib.cleanSource config.clan.directory; + + inputsForMachine = + machine: + flip mapAttrsToList machine.clan.core.vars.generators (_name: generator: generator.runtimeInputs); + + generatorRuntimeInputs = unique ( + flatten (flip mapAttrsToList config.nodes (_machineName: machine: inputsForMachine machine)) + ); + + vars-check = + hostPkgs.runCommand "update-vars-check" + { + nativeBuildInputs = generatorRuntimeInputs ++ [ + hostPkgs.nix + hostPkgs.git + hostPkgs.age + hostPkgs.sops + hostPkgs.bubblewrap + ]; + closureInfo = hostPkgs.closureInfo { + rootPaths = generatorRuntimeInputs ++ [ + hostPkgs.bash + hostPkgs.coreutils + hostPkgs.jq.dev + hostPkgs.stdenv + hostPkgs.stdenvNoCC + hostPkgs.shellcheck-minimal + hostPkgs.age + hostPkgs.sops + ]; + }; + } + '' + ${self.legacyPackages.${hostPkgs.system}.setupNixInNix} + cp -r ${testSrc} ./src + chmod +w -R ./src + find ./src/sops ./src/vars | sort > filesBefore + ${update-vars-script} ./src ${testName} \ + --repo-root ${self.packages.${hostPkgs.system}.clan-core-flake} \ + --clean + find ./src/sops ./src/vars | sort > filesAfter + if ! diff -q filesBefore filesAfter; then + echo "The update-vars script changed the files in ${testSrc}." + echo "Diff:" + diff filesBefore filesAfter || true + exit 1 + fi + touch $out + ''; + in + { + imports = [ + ../test/container-test-driver/driver-module.nix + ]; + 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 + { + _prefix = [ + "checks" + "" + config.name + "config" + "clan" + ]; + } + { + options = { + test.useContainers = mkOption { + default = true; + type = types.bool; + description = "Whether to use containers for the test."; + }; + }; + } + ]; + }; + }; + }; + config = { + # Inherit all nodes from the clan + # i.e. nodes.jon <- clan.machines.jon + # clanInternals.nixosModules contains nixosModules per node + nodes = clanFlakeResult.clanInternals.nixosModules; + + # !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 + + # 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; + # make the test depend on its vars-check derivation + environment.variables.CLAN_VARS_CHECK = "${vars-check}"; + } + ); + + result = { inherit update-vars vars-check; }; + }; + }; +} diff --git a/lib/flake-module.nix b/lib/flake-module.nix index abf55216a..b34c8fc12 100644 --- a/lib/flake-module.nix +++ b/lib/flake-module.nix @@ -8,6 +8,7 @@ rec { # TODO: automatically generate this from the directory conventions imports = [ ./build-clan/flake-module.nix + ./clanTest/flake-module.nix ./introspection/flake-module.nix ./inventory/flake-module.nix ./jsonschema/flake-module.nix diff --git a/lib/inventory/distributed-service/tests/default.nix b/lib/inventory/distributed-service/tests/default.nix index 8f2b42224..b8bb70c59 100644 --- a/lib/inventory/distributed-service/tests/default.nix +++ b/lib/inventory/distributed-service/tests/default.nix @@ -8,7 +8,6 @@ let evalModules ; - # TODO: Use makeTestClan evalInventory = m: (evalModules { diff --git a/lib/test/container-test-driver/driver-module.nix b/lib/test/container-test-driver/driver-module.nix index 065f07526..822f7dd55 100644 --- a/lib/test/container-test-driver/driver-module.nix +++ b/lib/test/container-test-driver/driver-module.nix @@ -36,7 +36,7 @@ let machineNames = map (name: "${name}: Machine;") pythonizedNames; pythonizedNames = map pythonizeName nodeHostNames; in -{ +lib.mkIf (config.clan.test.useContainers or true) { defaults.imports = [ ./nixos-module.nix ]; diff --git a/lib/test/default.nix b/lib/test/default.nix index 66260142d..466754287 100644 --- a/lib/test/default.nix +++ b/lib/test/default.nix @@ -1,217 +1,13 @@ { - lib, clanLib, + ... }: -let - inherit (lib) - mkOption - removePrefix - types - mapAttrsToList - flip - unique - flatten - ; - -in { - # containerTest = import ./container-test.nix; baseTest = import ./test-base.nix; - # + flakeModules = clanLib.callLib ./flakeModules.nix { }; minifyModule = ./minify.nix; sopsModule = ./sops.nix; - # A function that returns an extension to runTest - # TODO: remove this from clanLib, add to legacyPackages, simplify signature - makeTestClan = - { - nixosTest, - pkgs, - self, - useContainers ? true, - # Displayed for better error messages, otherwise the placeholder - attrName ? "", - ... - }: - let - nixos-lib = import (pkgs.path + "/nixos/lib") { }; - - testName = test.config.name; - - update-vars-script = "${self.packages.${pkgs.system}.generate-test-vars}/bin/generate-test-vars"; - - relativeDir = removePrefix ("${self}/") (toString test.config.clan.directory); - - update-vars = pkgs.writeShellScriptBin "update-vars" '' - ${update-vars-script} $PRJ_ROOT/${relativeDir} ${testName} - ''; - - testSrc = lib.cleanSource test.config.clan.directory; - - inputsForMachine = - machine: - flip mapAttrsToList machine.clan.core.vars.generators (_name: generator: generator.runtimeInputs); - - generatorRuntimeInputs = unique ( - flatten (flip mapAttrsToList test.config.nodes (_machineName: machine: inputsForMachine machine)) - ); - - vars-check = - pkgs.runCommand "update-vars-check" - { - nativeBuildInputs = generatorRuntimeInputs ++ [ - pkgs.nix - pkgs.git - pkgs.age - pkgs.sops - pkgs.bubblewrap - ]; - closureInfo = pkgs.closureInfo { - rootPaths = generatorRuntimeInputs ++ [ - pkgs.bash - pkgs.coreutils - pkgs.jq.dev - pkgs.stdenv - pkgs.stdenvNoCC - pkgs.shellcheck-minimal - pkgs.age - pkgs.sops - ]; - }; - } - '' - ${self.legacyPackages.${pkgs.system}.setupNixInNix} - cp -r ${testSrc} ./src - chmod +w -R ./src - find ./src/sops ./src/vars | sort > filesBefore - ${update-vars-script} ./src ${testName} \ - --repo-root ${self.packages.${pkgs.system}.clan-core-flake} \ - --clean - find ./src/sops ./src/vars | sort > filesAfter - if ! diff -q filesBefore filesAfter; then - echo "The update-vars script changed the files in ${testSrc}." - echo "Diff:" - diff filesBefore filesAfter || true - exit 1 - fi - touch $out - ''; - test = - (nixos-lib.runTest ( - { config, ... }: - let - clanFlakeResult = config.clan; - in - { - imports = [ - nixosTest - ] ++ lib.optionals useContainers [ ./container-test-driver/driver-module.nix ]; - 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 - { - _prefix = [ - "checks" - "" - attrName - "config" - "clan" - ]; - } - ]; - }; - }; - }; - 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; - - # !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 - - # 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; - # make the test depend on its vars-check derivation - environment.variables.CLAN_VARS_CHECK = "${vars-check}"; - } - ); - - # 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; - in - test - // { - inherit update-vars vars-check; - }; } diff --git a/pkgs/generate-test-vars/generate_test_vars/cli.py b/pkgs/generate-test-vars/generate_test_vars/cli.py index 61440b261..d82889330 100755 --- a/pkgs/generate-test-vars/generate_test_vars/cli.py +++ b/pkgs/generate-test-vars/generate_test_vars/cli.py @@ -93,8 +93,8 @@ class Options: def parse_args() -> Options: parser = argparse.ArgumentParser( description=""" - Update the vars of a 'makeTestClan' integration test. - See 'clanLib.test.makeTestClan' for more information on how to create such a test. + Update the vars of a 'clanTest' integration test. + See 'clanLib.test.clanTest' for more information on how to create such a test. """, ) parser.add_argument(