diff options
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/README.md | 2 | ||||
| -rw-r--r-- | tests/by-name/at/atuin-sync/test.nix | 141 | ||||
| -rw-r--r-- | tests/by-name/ba/back/test.nix | 82 | ||||
| -rw-r--r-- | tests/by-name/em/email-dns/nodes/name_server.nix | 2 | ||||
| -rw-r--r-- | tests/by-name/em/email-dns/test.nix | 23 | ||||
| -rw-r--r-- | tests/by-name/em/email-http/test.nix | 24 | ||||
| -rw-r--r-- | tests/by-name/em/email-ip/test.nix | 3 | ||||
| -rw-r--r-- | tests/by-name/gi/git-server/test.nix | 60 | ||||
| -rw-r--r-- | tests/by-name/mo/monitoring-basic/test.nix | 75 | ||||
| -rw-r--r-- | tests/by-name/mo/monitoring-federation/test.nix | 109 | ||||
| -rw-r--r-- | tests/by-name/ro/rocie/secrets/login.age | 16 | ||||
| -rw-r--r-- | tests/by-name/ro/rocie/test.nix | 47 | ||||
| -rw-r--r-- | tests/by-name/ru/rust-motd/test.nix | 38 | ||||
| -rw-r--r-- | tests/by-name/sh/sharkey-cpu/test.nix | 68 | ||||
| -rw-r--r-- | tests/by-name/sh/sharkey/test.nix | 103 | ||||
| -rw-r--r-- | tests/by-name/ta/taskchampion-sync/test.nix | 52 | ||||
| -rw-r--r-- | tests/common/acme/default.nix (renamed from tests/common/acme/scripts.nix) | 26 | ||||
| -rw-r--r-- | tests/default.nix | 258 |
18 files changed, 849 insertions, 280 deletions
diff --git a/tests/README.md b/tests/README.md index 7811f32..aaa76b4 100644 --- a/tests/README.md +++ b/tests/README.md @@ -1,6 +1,6 @@ # Tests This directory tree mirrors the modules defined in the -[modules](%60../modules/%60) directory. Each module should have at least +[modules](%60../modules/%60) directory. Each module should have at least one test in the mirrored directory, effectively replacing the module's `module.nix` file. diff --git a/tests/by-name/at/atuin-sync/test.nix b/tests/by-name/at/atuin-sync/test.nix new file mode 100644 index 0000000..627e89e --- /dev/null +++ b/tests/by-name/at/atuin-sync/test.nix @@ -0,0 +1,141 @@ +{ + nixos-lib, + pkgsUnstable, + nixpkgs-unstable, + vhackPackages, + pkgs, + extraModules, + nixLib, + turtle, + vhack, + ... +}: +vhack.runTest { + name = "atuin-sync"; + serverDomains = [ + { + server = "atuin-sync.server"; + } + ]; + + nodes = let + atuinSession = "01969ec6b8d07e30a9d2df0911fbfe2a"; + atuin = turtle.packages."${pkgs.stdenv.hostPlatform.system}".default; + in { + server = {config, ...}: { + imports = + extraModules + ++ [ + ../../../../modules + ]; + + vhack = { + persist.enable = true; + nginx.enable = true; + atuin-sync = { + enable = true; + fqdn = "atuin-sync.server"; + }; + }; + }; + + client1 = {config, ...}: { + environment.sessionVariables.ATUIN_SESSION = atuinSession; + + environment.systemPackages = [ + atuin + pkgs.sqlite-interactive + ]; + }; + client2 = {config, ...}: { + environment.sessionVariables.ATUIN_SESSION = atuinSession; + + environment.systemPackages = [ + atuin + pkgs.sqlite-interactive + ]; + }; + }; + + services = [ + {server = "turtle.service";} + ]; + + testScript = {nodes, ...}: let + mkSyncConfig = pkgs.writeShellScript "register-atuin-sync-account" '' + mkdir --parents ~/.config/atuin/ + + cat << EOF > ~/.config/atuin/config.toml + + [sync] + address = "https://atuin-sync.server" + user_id_path = "${pkgs.writeText "user-id" "019eb88a-6b51-7e52-b12c-7d30bd8e5928"}" + encryption_key_path = "${pkgs.writeText "encryption-key" "3AAgbWsDzL7M00/Mq0LMjsyOCy3MnsypBsyQzKbMywNGzNnMrUBozIINAxdbIiDMhQ=="}" + EOF + ''; + + runCommandAndRecordInAtuin = pkgs.writeShellScript "run-command-and-record-in-atuin" '' + # SPDX-SnippetBegin + # SPDX-SnippetCopyrightText: 2023 mentalisttraceur (https://github.com/mentalisttraceur) + # Source: https://github.com/atuinsh/atuin/issues/1188#issuecomment-1698354107 + run_and_record_in_atuin() + { + local id + local status + local escaped_command="$(printf '%q ' "$@")" + id="$(atuin history start -- "$escaped_command")" + "$@" + status=$? + atuin history end --exit $status "$id" + return $status + } + # SPDX-SnippetEnd + + run_and_record_in_atuin "$@" + ''; + in + # Python + '' + server.wait_for_open_port(443) + + # Wait for the server to acquire the acme certificate + client1.wait_until_succeeds("curl https://atuin-sync.server") + + with subtest("Setup client syncing"): + # See https://docs.atuin.sh/guide/sync/ + for client in [client1, client2]: + client.succeed("${mkSyncConfig}") + + with subtest("Start atuin daemons"): + for client in [client1, client2]: + client.succeed("systemd-run atuin daemon start") + + for client in [client1, client2]: + client.wait_until_succeeds("atuin daemon status") + + with subtest("Can import shell history"): + client1.succeed("${runCommandAndRecordInAtuin} echo hi - client 1") + client2.succeed("${runCommandAndRecordInAtuin} echo hi - client 2") + + with subtest("Can sync tasks"): + for client in [client1, client2]: + client.succeed("atuin sync perform --force") + client1.succeed("atuin sync perform --force") + + + with subtest("Have correct tasks"): + hist1 = client1.succeed("atuin history list --session --format '{command}'").strip().split('\n') + hist2 = client2.succeed("atuin history list --session --format '{command}'").strip().split('\n') + + hist1.sort() + hist2.sort() + + canonicalHistory = [ + "echo hi - client 1", + "echo hi - client 2" + ] + + assert hist1 == hist2, f"The clients don't have the same amount of history items, client1: '{hist1}', client2: '{hist2}'" + assert hist1 == canonicalHistory, f"The history is not correct: '{hist1}' vs. '{canonicalHistory}'" + ''; +} diff --git a/tests/by-name/ba/back/test.nix b/tests/by-name/ba/back/test.nix index 85cb611..b1d908d 100644 --- a/tests/by-name/ba/back/test.nix +++ b/tests/by-name/ba/back/test.nix @@ -1,11 +1,7 @@ { - nixos-lib, - pkgsUnstable, - nixpkgs-unstable, - vhackPackages, pkgs, extraModules, - nixLib, + vhack, ... }: let domain = "server"; @@ -22,17 +18,15 @@ option user-configs = cgit\.owner cgit\.desc cgit\.section cgit\.homepage ''; in - nixos-lib.runTest { - hostPkgs = pkgs; # the Nixpkgs package set used outside the VMs - + vhack.runTest { name = "back"; - node = { - specialArgs = {inherit pkgsUnstable vhackPackages nixpkgs-unstable nixLib;}; + ignore = "We are not using back currently."; - # Use the nixpkgs as constructed by the `nixpkgs.*` options - pkgs = null; - }; + serverDomains = [ + {server = "git.${domain}";} + {server = "issues.${domain}";} + ]; nodes = { server = {config, ...}: { @@ -49,21 +43,15 @@ in openssh.enable = true; nginx = { enable = true; - selfsign = true; }; git-server = { enable = true; domain = "git.${domain}"; gitolite.adminPubkey = sshKeys.admin.pub; }; - back = { + git-back = { enable = true; domain = "issues.${domain}"; - - settings = { - scan_path = "${config.services.gitolite.dataDir}/repositories"; - project_list = "${config.services.gitolite.dataDir}/projects.list"; - }; }; }; }; @@ -78,23 +66,17 @@ in PreferredAuthentications publickey ''; users.users.alice = {isNormalUser = true;}; - networking.hosts = { - "${nodes.server.networking.primaryIPAddress}" = [ - "git.${domain}" - "issues.${domain}" - "${domain}" - ]; - }; }; }; + services = [ + {server = "gitolite-init.service";} + {server = "sshd.service";} + ]; + testScript = {nodes, ...}: - /* - python - */ + # Python '' - start_all() - with subtest("can setup ssh keys on client"): client.succeed( "mkdir -p ~root/.ssh", @@ -108,12 +90,15 @@ in ) with subtest("gitolite server starts"): - server.wait_for_unit("gitolite-init.service") - server.wait_for_unit("sshd.service") client.succeed("ssh -n git@git.${domain} info") - with subtest("admin can clone and configure gitolite-admin.git"): + server.succeed("sudo -u git ${pkgs.writeShellScript "delete_main_branch_on_server" '' + set -xe + + cd ~git/repositories/gitolite-admin.git + git branch --move --force main master + ''}") client.succeed("${pkgs.writeShellScript "setup-gitolite-admin.git" '' set -xe @@ -123,12 +108,9 @@ in cp ${sshKeys.alice.pub} gitolite-admin/keydir/alice.pub - (cd gitolite-admin && git switch -c master && git branch -D main) - (cd gitolite-admin && git add . && git commit -m 'Add keys for alice' && git push -u origin master) cat ${gitoliteAdminConfSnippet} >> gitolite-admin/conf/gitolite.conf (cd gitolite-admin && git add . && git commit -m 'Add support for wild repos' && git push) - (cd gitolite-admin && git push -d origin main) ''}") with subtest("alice can create a repo"): @@ -152,35 +134,35 @@ in cd alice/repo1 - git bug user create --avatar "" --email "alice@server.org" --name "alice" --non-interactive + git bug user new --avatar "" --email "alice@server.org" --name "alice" --non-interactive - git bug add \ + git bug bug new \ --title "Some bug title" \ --message "A long description of the bug. Probably has some code segments, maybe even *markdown* mark_up_ or other things" \ --non-interactive - git bug add \ + git bug bug new \ --title "Second bug title" \ --message "" \ --non-interactive - git bug add \ + git bug bug new \ --title "Third bug title" \ --message "" \ --non-interactive - git bug select "$(git bug ls --format plain | awk '{print $1}' | head -n 1)" + git bug bug select "$(git bug bug --format plain | awk '{print $1}' | head -n 1)" - git bug comment add --message "Some comment message" --non-interactive - git bug comment add --message "Second comment message" --non-interactive + git bug bug comment new --message "Some comment message" --non-interactive + git bug bug comment new --message "Second comment message" --non-interactive # TODO: This should use `git bug push`, but their ssh implementation is just # too special to work in a VM test <2025-03-08> git push origin +refs/bugs/* git push origin +refs/identities/* - ssh git@${domain} -- config alice/repo1 --add cgit.owner Alice - ssh git@${domain} -- perms alice/repo1 + READERS @all + ssh git@git.${domain} -- config alice/repo1 --add cgit.owner Alice + ssh git@git.${domain} -- perms alice/repo1 + READERS @all ''}") with subtest("back server starts"): @@ -190,12 +172,12 @@ in client.succeed("${pkgs.writeShellScript "curl-back" '' set -xe - curl --insecure --fail --show-error "https://issues.${domain}/alice/repo1.git/issues/open" --output /root/issues.html + curl --fail --show-error "https://issues.${domain}/alice/repo1/issues/?query=status:open" --output /root/issues.html grep -- 'Second bug title' /root/issues.html - curl --insecure --fail --show-error "https://issues.${domain}/" --output /root/repos.html + curl --fail --show-error "https://issues.${domain}/" --output /root/repos.html grep -- 'repo' /root/repos.html - grep -- "<No description>" /root/repos.html + grep -- "<No description>" /root/repos.html grep -- '<span class="user-name">Alice</span>' /root/repos.html ''} >&2") diff --git a/tests/by-name/em/email-dns/nodes/name_server.nix b/tests/by-name/em/email-dns/nodes/name_server.nix index d9d3617..bde1a16 100644 --- a/tests/by-name/em/email-dns/nodes/name_server.nix +++ b/tests/by-name/em/email-dns/nodes/name_server.nix @@ -63,7 +63,7 @@ adkim = "strict"; aspf = "strict"; fo = ["0" "1" "d" "s"]; - p = "quarantine"; + p = "reject"; rua = cfg.admin; ruf = [cfg.admin]; } diff --git a/tests/by-name/em/email-dns/test.nix b/tests/by-name/em/email-dns/test.nix index f0399a5..33955d8 100644 --- a/tests/by-name/em/email-dns/test.nix +++ b/tests/by-name/em/email-dns/test.nix @@ -14,6 +14,9 @@ inherit (user) mkUser; in nixos-lib.runTest { + # Wait until we actually use stalwart again + meta.broken = true; + hostPkgs = pkgs; # the Nixpkgs package set used outside the VMs name = "email-dns"; @@ -90,23 +93,13 @@ in } ''; - acme_scripts = import ../../../common/acme/scripts.nix {inherit pkgs;}; + acme = import ../../../common/acme {inherit pkgs;}; in - /* - python - */ + acme.prepare ["mail1_server" "mail2_server" "alice" "bob"] + # Python '' from time import sleep - # Start dependencies for the other services - acme.start() - acme.wait_for_unit("pebble.service") - name_server.start() - name_server.wait_for_unit("nsd.service") - - # Start the actual testing machines - start_all() - mail1_server.wait_for_unit("stalwart-mail.service") mail1_server.wait_for_open_port(993) # imap mail1_server.wait_for_open_port(465) # smtp @@ -120,10 +113,6 @@ in name_server.wait_until_succeeds("stat /var/lib/acme/mta-sts.alice.com/cert.pem") name_server.wait_until_succeeds("stat /var/lib/acme/mta-sts.bob.com/cert.pem") - with subtest("Add pebble ca key to all services"): - for node in [name_server, mail1_server, mail2_server, alice, bob]: - node.succeed("${acme_scripts.add_pebble_acme_ca}") - with subtest("Both mailserver successfully started all services"): import json def all_services_running(host): diff --git a/tests/by-name/em/email-http/test.nix b/tests/by-name/em/email-http/test.nix index f508b9f..42fd22b 100644 --- a/tests/by-name/em/email-http/test.nix +++ b/tests/by-name/em/email-http/test.nix @@ -14,6 +14,9 @@ inherit (user) mkUser; in nixos-lib.runTest { + # Wait until we actually use stalwart again + meta.broken = true; + hostPkgs = pkgs; # the Nixpkgs package set used outside the VMs name = "email-http"; @@ -71,32 +74,17 @@ in # TODO(@bpeetz): This test should also test the http JMAP features of stalwart-mail. <2025-04-12> testScript = _: let - acme_scripts = import ../../../common/acme/scripts.nix {inherit pkgs;}; + acme = import ../../../common/acme {inherit pkgs;}; in - /* - python - */ + acme.prepare ["mail_server" "bob"] + # Python '' - # Start dependencies for the other services - acme.start() - acme.wait_for_unit("pebble.service") - name_server.start() - name_server.wait_for_unit("nsd.service") - - # Start the actual testing machines - start_all() - mail_server.wait_for_unit("stalwart-mail.service") mail_server.wait_for_open_port(993) # imap mail_server.wait_for_open_port(465) # smtp bob.wait_for_unit("multi-user.target") - with subtest("Add pebble ca key to all services"): - for node in [name_server, mail_server, bob]: - node.wait_for_unit("network-online.target") - node.succeed("${acme_scripts.add_pebble_acme_ca}") - with subtest("The mailserver successfully started all services"): import json def all_services_running(host): diff --git a/tests/by-name/em/email-ip/test.nix b/tests/by-name/em/email-ip/test.nix index dabc404..c0b2d7e 100644 --- a/tests/by-name/em/email-ip/test.nix +++ b/tests/by-name/em/email-ip/test.nix @@ -78,6 +78,9 @@ }; in nixos-lib.runTest { + # Wait until we actually use stalwart again + meta.broken = true; + hostPkgs = pkgs; # the Nixpkgs package set used outside the VMs name = "email"; diff --git a/tests/by-name/gi/git-server/test.nix b/tests/by-name/gi/git-server/test.nix index 5cd8c33..6fc3685 100644 --- a/tests/by-name/gi/git-server/test.nix +++ b/tests/by-name/gi/git-server/test.nix @@ -5,6 +5,7 @@ pkgs, extraModules, nixLib, + vhack, ... }: let sshKeys = @@ -35,24 +36,16 @@ option user-configs = cgit\.owner cgit\.desc cgit\.section cgit\.homepage ''; - expectedHtmlReadme = pkgs.writeText "expectedHtmlReadme" '' - <h1>Alice's Repo</h1> - ''; expectedMdReadme = pkgs.writeText "expectedMdReadme" '' # Alice's Repo ''; in - nixos-lib.runTest { - hostPkgs = pkgs; # the Nixpkgs package set used outside the VMs - + vhack.runTest { name = "git-server"; - node = { - specialArgs = {inherit pkgsUnstable nixpkgs-unstable nixLib;}; - - # Use the nixpkgs as constructed by the `nixpkgs.*` options - pkgs = null; - }; + serverDomains = [ + {server = gitServerDomain;} + ]; nodes = { server = {config, ...}: { @@ -67,7 +60,6 @@ in openssh.enable = true; nginx = { enable = true; - selfsign = true; }; git-server = { enable = true; @@ -91,13 +83,14 @@ in }; }; - testScript = {nodes, ...}: - /* - python - */ - '' - start_all() + services = [ + {server = "gitolite-init.service";} + {server = "sshd.service";} + ]; + testScript = {...}: + # Python + '' with subtest("can setup ssh keys on client"): client.succeed( "mkdir -p ~root/.ssh", @@ -116,12 +109,16 @@ in ) with subtest("gitolite server starts"): - server.wait_for_unit("gitolite-init.service") - server.wait_for_unit("sshd.service") client.succeed("ssh -n git@server info") with subtest("admin can clone and configure gitolite-admin.git"): + server.succeed("sudo -u git ${pkgs.writeShellScript "delete_main_branch_on_server" '' + set -xe + + cd ~git/repositories/gitolite-admin.git + git branch --move --force main master + ''}") client.succeed("${pkgs.writeShellScript "setup-gitolite-admin.git" '' set -xe @@ -132,12 +129,9 @@ in cp ${sshKeys.alice.pub} gitolite-admin/keydir/alice.pub cp ${sshKeys.bob.pub} gitolite-admin/keydir/bob.pub - (cd gitolite-admin && git switch -c master && git branch -D main) - (cd gitolite-admin && git add . && git commit -m 'Add keys for alice, bob' && git push -u origin master) cat ${gitoliteAdminConfSnippet} >> gitolite-admin/conf/gitolite.conf (cd gitolite-admin && git add . && git commit -m 'Add support for wild repos' && git push) - (cd gitolite-admin && git push -d origin main) ''}") server.succeed("${pkgs.writeShellScript "verify gitolite-admin.conf" '' @@ -202,7 +196,7 @@ in cd ~bob # Disable ssl verification, as the certs are self-signed - git -c http.sslVerify=false clone https://server/alice/alice-project.git + git -c http.sslVerify=false clone https://server/alice/alice-project ''}") with subtest("Alice can change settings in her repo"): @@ -221,15 +215,13 @@ in } ''}") + with subtest("Bob can see alice's README"): + client.succeed("sudo -u bob ${pkgs.writeShellScript "bob-alice-readme" '' + set -xe - # He can't see the readme (FIXME: find out why this does not work. <2024-08-13> ) - # with subtest("Bob can see alice's README"): - # client.succeed("sudo -u bob ${pkgs.writeShellScript "bob-alice-readme" '' - # set -xe - # - # curl --insecure --silent --fail --show-error 'https://server/alice/alice-project/about' > readme.html - # cat readme.html - # diff --side-by-side ${expectedHtmlReadme} readme.html - # ''}") + curl --fail --show-error 'https://server/alice/alice-project/about/' > readme.html + grep 'alice-project - My nice project.' ./readme.html + grep 'My nice project.' ./readme.html + ''}") ''; } diff --git a/tests/by-name/mo/monitoring-basic/test.nix b/tests/by-name/mo/monitoring-basic/test.nix new file mode 100644 index 0000000..a00331f --- /dev/null +++ b/tests/by-name/mo/monitoring-basic/test.nix @@ -0,0 +1,75 @@ +{ + extraModules, + vhack, + ... +}: let + grafanaDomain = "grafana.server.org"; + scrutinyDomain = "scrutiny.server.org"; +in + vhack.runTest { + name = "monitoring"; + + serverDomains = [ + {server = grafanaDomain;} + {server = scrutinyDomain;} + ]; + + nodes = { + client = {}; + + server = {config, ...}: { + imports = + extraModules + ++ [ + ../../../../modules + ]; + + # there are no SMART available disk in the VM, so this service always fails. + systemd.services."smartd".enable = false; + + age.identityPaths = ["${../../../common/email/hostKey}"]; + + vhack = { + monitoring = { + grafana = { + enable = true; + contactPoints = ["me@example.com"]; + fqdn = grafanaDomain; + + adminPassword = ../../../common/email/dkim/alice.com/private.age; + secretKey = ../../../common/email/dkim/bob.com/private.age; + + # TODO: Add a test for that <2026-07-19> + smtp = null; + }; + + loki = { + enable = true; + }; + + prometheus = { + enable = true; + }; + + scrutiny = { + enable = true; + fqdn = scrutinyDomain; + }; + }; + }; + }; + }; + + services = [ + {server = "grafana.service";} + {server = "fluent-bit.service";} + {server = "loki.service";} + {server = "netdata.service";} + ]; + + testScript = {...}: + # Python + '' + # TODO: We should probably query some of the prometheus metrics here? <2026-07-19> + ''; + } diff --git a/tests/by-name/mo/monitoring-federation/test.nix b/tests/by-name/mo/monitoring-federation/test.nix new file mode 100644 index 0000000..a64577d --- /dev/null +++ b/tests/by-name/mo/monitoring-federation/test.nix @@ -0,0 +1,109 @@ +{ + extraModules, + pkgs, + vhack, + ... +}: let + prometheusMain = "prometheus.server2.server.org"; + remoteWriteTo = { + url = "https://${prometheusMain}"; + }; + promPort = 3001; +in + vhack.runTest { + name = "monitoring"; + + serverDomains = [ + {server2 = prometheusMain;} + ]; + + nodes = { + server2 = {config, ...}: { + imports = + extraModules + ++ [ + ../../../../modules + ]; + + environment.systemPackages = [ + pkgs.curl + ]; + + vhack = { + monitoring = { + prometheus = { + enable = true; + port = promPort; + remoteWriteReceiver = prometheusMain; + }; + }; + }; + }; + server3 = {config, ...}: { + imports = + extraModules + ++ [ + ../../../../modules + ]; + + vhack = { + monitoring = { + prometheus = { + enable = true; + port = promPort; + inherit remoteWriteTo; + }; + }; + }; + }; + server4 = {config, ...}: { + imports = + extraModules + ++ [ + ../../../../modules + ]; + + vhack = { + monitoring = { + prometheus = { + enable = true; + port = promPort; + inherit remoteWriteTo; + }; + }; + }; + }; + }; + + services = [ + {server2 = "prometheus.service";} + {server3 = "prometheus.service";} + {server4 = "prometheus.service";} + ]; + + testScript = {...}: + # Python + '' + import json, time + + # Give the Prometheus servers some time to generate metrics and sync with the main + # one. + time.sleep(1) + + metrics = server2.succeed("${pkgs.writeShellScript "query-metrics" '' + curl --silent \ + http://127.0.0.1:${toString promPort}/api/v1/query?query="go_gc_cleanups_queued_cleanups_total" + ''}") + + metrics = json.loads(metrics) + + hosts = [] + for result in metrics["data"]["result"]: + server2.log(json.dumps(result,indent=4)) + hosts.append(result["metric"]["hostname"]) + + hosts.sort() + + assert hosts == ["server2", "server3", "server4"], f"Not all hosts used the remote write (got {hosts})" + ''; + } diff --git a/tests/by-name/ro/rocie/secrets/login.age b/tests/by-name/ro/rocie/secrets/login.age new file mode 100644 index 0000000..33d63be --- /dev/null +++ b/tests/by-name/ro/rocie/secrets/login.age @@ -0,0 +1,16 @@ +-----BEGIN AGE ENCRYPTED FILE----- +YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAzMWE5dTBiU0hDUC9jUi93 +Y1phYllHRk9YSHBzUGQ2YmF5ZC9ydXNGV0JrClRpTjZZUHZ5MEFFa0VrYVVhTkE2 +eCtSaEU1YVlhNjFNYlRRYzNCdjhYRWMKLT4gWDI1NTE5IDUrZWdDUmpQcFBOcE0x +UE5QRDR5NXVXUHdQOVk3UGV1S3lCc0pUQmZIZ00KUldVSVF3TzB0cHFVaDZuNlZR +b2FoT0lVSTZydHFTNHhnQ3U0NGdSR1k1MAotPiBzc2gtZWQyNTUxOSBSc2dXcFEg +dldGZU15UXgrRTMxRkp2MEVKUllWQ3VFdnJDMnM4OS8wc202WW9lNW5BYwpPWjV0 +cmNuaDlPZndtUVVScm5TaGlvVUhHa0JiN1MvbDhCTTUxYzNhM3RRCi0+IDxXeEhv +cC1ncmVhc2UKM3N5OHRLNTJEY1NIeGlWYm9yR096Y1NpSlVOM1lYQk9jOHkxU3N2 +K2c3QitDYnR6QTJOOWczV0xBa2dEUE1PTQpYU2Z1elZwRzU0Tm1RVDE2VWVqekUw +bFROLzU0c2NNTXYwL2N5QkxTaGtXUWxKVVF6SE0KLS0tIGlYMHIvUkJpZUR0SHo4 +cldLSTdnbU90SGJTcGZGaHkyOTZON0hka3BLdlEKeP4nHmKWvJfqgEXuiLBMzldi +n1qIsnlF3IU1EA0abJg/RK1BFwWlx4wBlLmViw6UTL+VEw8lv23PuZl2t7UtXVzQ +smXDapW8nInNmTaElBPdwJ072/dD0Ly+KF95Qr0FDDv+jlKG/D/Mw+xD4jvuJHSo +2HQnPF6MLTjCxpyPPggleWgKrBQggHBjm/pHtOKmPC5qfp+LAjmQoJXny/0X6cA= +-----END AGE ENCRYPTED FILE----- diff --git a/tests/by-name/ro/rocie/test.nix b/tests/by-name/ro/rocie/test.nix new file mode 100644 index 0000000..1f0fccb --- /dev/null +++ b/tests/by-name/ro/rocie/test.nix @@ -0,0 +1,47 @@ +{ + extraModules, + vhack, + ... +}: +vhack.runTest { + name = "rocie"; + + serverDomains = [ + {server = "rocie.server";} + ]; + + nodes = { + server = {config, ...}: { + imports = + extraModules + ++ [ + ../../../../modules + ]; + + age.identityPaths = ["${../../../common/email/hostKey}"]; + + vhack = { + persist.enable = true; + nginx.enable = true; + rocie = { + enable = true; + domain = "rocie.server"; + loginSecret = ./secrets/login.age; + }; + }; + }; + + client = {_, ...}: {}; + }; + + services = [ + {server = "rocie.service";} + ]; + + testScript = {...}: + # Python + '' + client.wait_until_succeeds("curl --verbose https://rocie.server/api/can-be-provisioned > out.file") + client.copy_from_vm("out.file") + ''; +} diff --git a/tests/by-name/ru/rust-motd/test.nix b/tests/by-name/ru/rust-motd/test.nix new file mode 100644 index 0000000..43a905d --- /dev/null +++ b/tests/by-name/ru/rust-motd/test.nix @@ -0,0 +1,38 @@ +{ + extraModules, + vhack, + ... +}: +vhack.runTest { + name = "rust-motd"; + + nodes = { + server = {config, ...}: { + imports = + extraModules + ++ [ + ../../../../modules + ]; + + vhack = { + rust-motd.enable = true; + }; + }; + }; + + services = [ + { + server = "rust-motd.service"; + start = true; + } + ]; + + testScript = {nodes, ...}: + # Python + '' + with subtest("Motd generated"): + server.succeed("cat /var/lib/rust-motd/motd | tee /dev/stderr | grep --invert-match --ignore-case Error") + + server.copy_from_vm("/var/lib/rust-motd/motd") + ''; +} diff --git a/tests/by-name/sh/sharkey-cpu/test.nix b/tests/by-name/sh/sharkey-cpu/test.nix index d4f9332..d648429 100644 --- a/tests/by-name/sh/sharkey-cpu/test.nix +++ b/tests/by-name/sh/sharkey-cpu/test.nix @@ -1,24 +1,11 @@ { - nixos-lib, - pkgsUnstable, - nixpkgs-unstable, - vhackPackages, pkgs, extraModules, - nixLib, + vhack, ... }: -nixos-lib.runTest { - hostPkgs = pkgs; # the Nixpkgs package set used outside the VMs - - name = "sharkey-images"; - - node = { - specialArgs = {inherit pkgsUnstable extraModules vhackPackages nixpkgs-unstable nixLib;}; - - # Use the nixpkgs as constructed by the `nixpkgs.*` options - pkgs = null; - }; +vhack.runTest { + name = "sharkey-cpu"; nodes = { server = {config, ...}: { @@ -38,19 +25,29 @@ nixos-lib.runTest { }; systemd.services = { # Avoid an error from this service. - "acme-sharkey.server".serviceConfig.ExecStart = pkgs.lib.mkForce "${pkgs.lib.getExe' pkgs.coreutils "true"}"; + "acme-sharkey.server".enable = false; - # Test, that sharkey's hardening still allows access to the CPUs. + # Test that sharkey's hardening still allows access to the CPUs. sharkey.serviceConfig.ExecStart = let - nodejs = pkgs.lib.getExe pkgsUnstable.nodejs; + nodejs = pkgs.lib.getExe pkgs.nodejs; script = pkgs.writeTextFile { name = "script.js"; - text = '' - import * as os from 'node:os'; + text = + # js + '' + import * as os from 'node:os'; + + var cpus = os.cpus() - console.log(os.cpus()[0].model) - console.log(os.cpus().length) - ''; + if (cpus.length != 0) { + console.log(cpus[0].model) + } else { + // Fail? + } + + while (true) { + } + ''; }; in pkgs.lib.mkForce "${nodejs} ${script}"; @@ -58,25 +55,16 @@ nixos-lib.runTest { }; }; - testScript = {nodes, ...}: - /* - python - */ + services = [ + {server = "sharkey.service";} + ]; + + testScript = {...}: + # Python '' from time import sleep - start_all() - server.wait_for_unit("sharkey.service") - - # Give the service time to start. + # Give the service time to run. sleep(3) - - with subtest("All services running"): - import json - def all_services_running(host): - (status, output) = host.systemctl("list-units --state=failed --plain --no-pager --output=json") - host_failed = json.loads(output) - assert len(host_failed) == 0, f"Expected zero failing services, but found: {json.dumps(host_failed, indent=4)}" - all_services_running(server) ''; } diff --git a/tests/by-name/sh/sharkey/test.nix b/tests/by-name/sh/sharkey/test.nix index 40efe17..7b6f537 100644 --- a/tests/by-name/sh/sharkey/test.nix +++ b/tests/by-name/sh/sharkey/test.nix @@ -1,66 +1,21 @@ { - nixos-lib, - pkgsUnstable, - nixpkgs-unstable, - vhackPackages, - pkgs, extraModules, - nixLib, + vhack, ... }: -nixos-lib.runTest { - hostPkgs = pkgs; # the Nixpkgs package set used outside the VMs - +vhack.runTest { name = "sharkey"; - node = { - specialArgs = {inherit pkgsUnstable extraModules vhackPackages nixpkgs-unstable nixLib;}; - - # Use the nixpkgs as constructed by the `nixpkgs.*` options - pkgs = null; - }; + serverDomains = [ + {server = "sharkey.server";} + ]; nodes = { - acme = {...}: { - imports = [ - ../../../common/acme/server.nix - ../../../common/dns/client.nix - ]; - }; - name_server = {nodes, ...}: { - imports = - extraModules - ++ [ - ../../../common/acme/client.nix - ../../../common/dns/server.nix - ]; - - vhack.dns.zones = { - "sharkey.server" = { - SOA = { - nameServer = "ns"; - adminEmail = "admin@server.com"; - serial = 2025012301; - }; - useOrigin = false; - - A = [ - nodes.server.networking.primaryIPAddress - ]; - AAAA = [ - nodes.server.networking.primaryIPv6Address - ]; - }; - }; - }; - server = {config, ...}: { imports = extraModules ++ [ ../../../../modules - ../../../common/acme/client.nix - ../../../common/dns/client.nix ]; vhack = { @@ -73,46 +28,16 @@ nixos-lib.runTest { }; }; - client = {...}: { - imports = [ - ../../../common/acme/client.nix - ../../../common/dns/client.nix - ]; - }; + client = {...}: {}; }; - testScript = {nodes, ...}: let - acme_scripts = import ../../../common/acme/scripts.nix {inherit pkgs;}; - in - /* - python - */ - '' - # Start dependencies for the other services - acme.start() - acme.wait_for_unit("pebble.service") - name_server.start() - name_server.wait_for_unit("nsd.service") - - # Start the actual testing machines - start_all() - - - with subtest("Add pebble ca key to all services"): - for node in [name_server, server, client]: - node.wait_for_unit("network-online.target") - node.succeed("${acme_scripts.add_pebble_acme_ca}") - - server.wait_for_unit("sharkey.service") - - with subtest("All services running"): - import json - def all_services_running(host): - (status, output) = host.systemctl("list-units --state=failed --plain --no-pager --output=json") - host_failed = json.loads(output) - assert len(host_failed) == 0, f"Expected zero failing services, but found: {json.dumps(host_failed, indent=4)}" - all_services_running(server) + services = [ + {server = "sharkey.service";} + ]; - client.wait_until_succeeds("curl --silent https://sharkey.server | grep 'Thank you for using Sharkey!'") - ''; + testScript = {...}: + # Python + '' + client.wait_until_succeeds("curl --verbose https://sharkey.server | grep 'Thank you for using Sharkey!'") + ''; } diff --git a/tests/by-name/ta/taskchampion-sync/test.nix b/tests/by-name/ta/taskchampion-sync/test.nix index 4dd273b..3878167 100644 --- a/tests/by-name/ta/taskchampion-sync/test.nix +++ b/tests/by-name/ta/taskchampion-sync/test.nix @@ -6,20 +6,21 @@ pkgs, extraModules, nixLib, + vhack, ... }: -nixos-lib.runTest { - hostPkgs = pkgs; +vhack.runTest { name = "taskchampion-sync"; - node = { - specialArgs = {inherit pkgsUnstable vhackPackages nixpkgs-unstable nixLib;}; + serverDomains = [ + {server = "taskchampion.server";} + ]; - # Use the nixpkgs as constructed by the `nixpkgs.*` options - pkgs = null; - }; - - nodes = { + nodes = let + taskwarriorPackage = pkgs.taskwarrior3.overrideAttrs (final: prev: { + cmakeFlags = (prev.cmakeFlags or []) ++ ["-DENABLE_TLS_NATIVE_ROOTS=true"]; + }); + in { server = {config, ...}: { imports = extraModules @@ -28,26 +29,32 @@ nixos-lib.runTest { ]; vhack = { - taskchampion-sync.enable = true; + persist.enable = true; + nginx.enable = true; + taskchampion-sync = { + enable = true; + fqdn = "taskchampion.server"; + }; }; }; task_client1 = {config, ...}: { environment.systemPackages = [ - pkgs.taskwarrior3 + taskwarriorPackage ]; }; task_client2 = {config, ...}: { environment.systemPackages = [ - pkgs.taskwarrior3 + taskwarriorPackage ]; }; }; - testScript = {nodes, ...}: let - cfg = nodes.server.services.taskchampion-sync-server; - port = builtins.toString cfg.port; + services = [ + {server = "taskchampion-sync-server.service";} + ]; + testScript = {nodes, ...}: let # Generated with uuidgen uuid = "bf01376e-04a4-435a-9263-608567531af3"; password = "nixos-test"; @@ -57,19 +64,14 @@ nixos-lib.runTest { set -xe mkdir --parents "$(dirname "${path}")" - echo 'sync.server.origin=http://server:${port}' >> "${path}" + echo 'sync.server.url=https://taskchampion.server' >> "${path}" echo 'sync.server.client_id=${uuid}' >> "${path}" echo 'sync.encryption_secret=${password}' >> "${path}" ''; in - /* - python - */ + # Python '' - start_all() - - server.wait_for_unit("taskchampion-sync-server.service") - server.wait_for_open_port(${port}) + server.wait_for_open_port(443) with subtest("Setup task syncing"): for task in [task_client1, task_client2]: @@ -81,11 +83,15 @@ nixos-lib.runTest { task_client1.succeed("task add 'First task -- task_client1'") task_client2.succeed("task add 'First task -- task_client2'") + # Wait for the server to acquire the acme certificate + task_client1.wait_until_succeeds("curl https://taskchampion.server") + with subtest("Can sync tasks"): for task in [task_client1, task_client2]: task.succeed("task sync") task_client1.succeed("task sync") + with subtest("Have correct tasks"): count1 = task_client1.succeed("task count") count2 = task_client2.succeed("task count") diff --git a/tests/common/acme/scripts.nix b/tests/common/acme/default.nix index 2228823..e7869c2 100644 --- a/tests/common/acme/scripts.nix +++ b/tests/common/acme/default.nix @@ -1,9 +1,5 @@ -{pkgs}: -/* -* Extra functions useful for the test script. -*/ -{ - add_pebble_acme_ca = pkgs.writeShellScript "fetch-and-set-ca" '' +{pkgs}: let + add_pebble_ca_certs = pkgs.writeShellScript "fetch-and-set-ca" '' set -xe # Fetch the randomly generated ca certificate @@ -27,4 +23,22 @@ # # P11-Kit trust source. # environment.etc."ssl/trust-source".source = "$${cacertPackage.p11kit}/etc/ssl/trust-source"; ''; +in { + prepare = clients: extra: + # The parens are needed for the syntax highlighting to work. + ( # python + '' + # Start dependencies for the other services + start_all() + + acme.wait_for_unit("pebble.service") + name_server.wait_for_unit("nsd.service") + + with subtest("Add pebble ca key to all services"): + for node in [name_server, ${builtins.concatStringsSep "," clients}]: + node.wait_until_succeeds("curl https://acme.test:15000/roots/0") + node.succeed("${add_pebble_ca_certs}") + '' + ) + + extra; } diff --git a/tests/default.nix b/tests/default.nix index d9b354a..9bab516 100644 --- a/tests/default.nix +++ b/tests/default.nix @@ -3,11 +3,267 @@ nixLib, pkgs, }: let + splitAttr = attr: let + name = builtins.elemAt (builtins.attrNames attr) 0; + in { + inherit name; + value = attr.${name}; + }; + + vhack.runTest = { + name, + nodes ? {}, + serverDomains ? [], + testScript ? {_, ...}: "", + services ? [], + ignore ? null, + }: let + # Apparently the default's (e.g. `serivces ? []`), are not applied for the `@args` + # syntax. + args = { + inherit name nodes serverDomains testScript services ignore; + }; + in + specialArgs.nixos-lib.runTest { + hostPkgs = pkgs; + inherit (args) name; + + meta.broken = pkgs.lib.mkIf (args.ignore != null) true; + + node = { + specialArgs = { + inherit + (specialArgs) + pkgsUnstable + vhackPackages + nixpkgs-unstable + ; + inherit nixLib; + }; + + # Use the nixpkgs as constructed by the `nixpkgs.*` options + pkgs = null; + }; + + nodes = pkgs.lib.mkMerge [ + { + acme = { + imports = [ + ./common/acme/server.nix + ./common/dns/client.nix + ]; + }; + name_server = {nodes, ...}: { + imports = + specialArgs.extraModules + ++ [ + ./common/acme/client.nix + ./common/dns/server.nix + ]; + + vhack.dns.zones = let + mkDomain = attr: let + split = splitAttr attr; + in { + name = "${split.value}"; + value = { + SOA = { + nameServer = "ns"; + adminEmail = "admin@server.com"; + serial = 2025012301; + }; + useOrigin = false; + + A = [ + nodes.${split.name}.networking.primaryIPAddress + ]; + AAAA = [ + nodes.${split.name}.networking.primaryIPv6Address + ]; + }; + }; + in + builtins.listToAttrs (builtins.map mkDomain args.serverDomains); + }; + } + args.nodes + (builtins.mapAttrs (_: _: { + imports = [ + ./common/acme/client.nix + ./common/dns/client.nix + ]; + }) + args.nodes) + ]; + + testScript = {nodes, ...} @ testScriptInput: let + acme = import ./common/acme {inherit pkgs;}; + + waitFor = builtins.concatStringsSep "\n" ( + builtins.map (attr: let + split = splitAttr attr; + in " maybe_wait(${split.name},\"${split.value}\", ${ + if (builtins.hasAttr "start" attr && attr.start) + then "True" + else "False" + })") + args.services + ); + + acmeOnline = builtins.concatStringsSep "\n" (builtins.map (attr: let + split = splitAttr attr; + in " status.append(acme_online(${split.name}, \"${split.value}\", renewRan.get(\"acme-order-renew-${split.value}.service\", False)))") + args.serverDomains); + + allRunning = + builtins.concatStringsSep "\n" (builtins.map (name: " all_services_running(${name})") + (builtins.attrNames args.nodes)); + + optional = condition: value: + if condition + then value + else ""; + in + acme.prepare (builtins.attrNames args.nodes) + # The acme code runs `start_all` before it hands execution back to us. + # So we don't need to run it. + ( + optional (args.services != [] || args.serverDomains != []) + # Python + '' + def unit_exists(host, unitName: str) -> bool: + (status, _) = host.execute(f"test -f '/etc/systemd/system/{unitName}'") + return status == 0 + + wasAlive: bool + deadCounter: int + def run_and_wait(host, unit: str): + global wasAlive + global deadCounter + + wasAlive = False + deadCounter = 0 + + def wait_state(_last_try: bool) -> bool: + global wasAlive + global deadCounter + + state = host.get_unit_property(unit, "SubState") + host.log(f"(waiting) {unit} -> {state}") + + match state: + case "failed": + assert False, f'Unit "{unit}" reached state "{state}"' + case "start" | "active" | "running": + wasAlive = True + case "dead": + if wasAlive: + return True + else: + if deadCounter >= 5: + return True + + deadCounter = deadCounter + 1 + return False + + return False + + with host.nested(f"Waiting for unit {unit}"): + retry(wait_state) + '' + + optional (args.serverDomains != []) + # Python + '' + def acme_online(host, domain, renewRan): + from typing import assert_never + + unitMain = f"acme-{domain}.service" + unitRenew = f"acme-order-renew-{domain}.service" + + infoMain = host.get_unit_property(unitMain, "SubState") + infoRenew = host.get_unit_property(unitRenew, "SubState") + + match (infoMain, infoRenew): + case ("exited", "dead") | ("dead", "dead") if not renewRan: + # The unit might not exist, because we use the DNS values for + # generating this code (there is no needed mapping between DNS -> acme + # host). So we check, if it is an actual unit. + if unit_exists(host, unitMain): + host.start_job(f"acme-{domain}") + return ("Wait", unitRenew, host) + else: + return ("Done", None, None) + + case ("exited", "dead") | ("exited", "dead") if renewRan: + # The unit is done, so it should be good? + host.require_unit_state(unitMain, "active") + host.succeed(f"${pkgs.lib.getExe pkgs.openssl} s_client -connect {domain}:443 -verify_return_error </dev/null") + return ("Done", None, None) + + case (_, "running") | (_, "start"): + # It's currently running, let's wait + return ("Wait", unitRenew, host) + + case other: + assert False, f"Wrong unit sub-states for ({unitMain},{unitRenew}): {other}" + assert_never() # i.e. unreachable + + with subtest("Ensure, that all acme certificates have been fetched"): + renewRan = {} + while True: + status = [] + ${acmeOnline} + done = True + for (state, unit, host) in status: + if state == "Wait": + run_and_wait(host, unit) + renewRan[unit] = True + if state != "Done": + done = False + if done: + break + '' + + optional (args.services != []) + # Python + '' + def maybe_wait(host, unit: str, should_start: bool): + if unit_exists(host, unit): + if should_start: + host.start_job(unit.removesuffix(".service")) + run_and_wait(host, unit) + else: + host.wait_for_unit(unit) + else: + assert False, f"Unit '{unit}' does not exist, test misconfigured" + + with subtest("Waiting for units"): + ${waitFor} + '' + + + # Python + '' + def all_services_running(host): + import json + (status, output) = host.systemctl("list-units --state=failed --plain --no-pager --output=json") + host_failed = json.loads(output) + assert len(host_failed) == 0, f"Expected zero failing services, but found: {json.dumps(host_failed, indent=4)}" + + with subtest("All services running"): + ${allRunning} + + ${(args.testScript testScriptInput)} + + with subtest("All services running"): + ${allRunning} + '' + ); + }; + tests = nixLib.mkByName { baseDirectory = ./by-name; fileName = "test.nix"; finalizeFunction = name: value: - import value (nixLib.warnMerge specialArgs {inherit pkgs;} "the test args set"); + import value (nixLib.warnMerge specialArgs {inherit pkgs vhack;} "the test args set"); }; in tests |
