Fediversity/deployment/check/common/nixosTest.nix
2025-05-08 15:55:25 +02:00

163 lines
5.4 KiB
Nix

{
lib,
config,
hostPkgs,
...
}:
let
sources = import ../../../npins;
inherit (builtins)
filter
;
inherit (lib)
fileset
mkOption
genAttrs
concatLists
attrNames
;
## Helpers to map over target machines and produce an attrset, a list, or a
## multiline string suitable for use in a Python script.
# forAllTargetMachines = lib.genAttrs config.targetMachines;
# forAllTargetMachines' = f: map f config.targetMachines;
forConcat = xs: f: builtins.concatStringsSep "\n" (map f xs);
inherit (config) targetMachines pathToRoot pathFromRoot;
## The whole repository.
## FIXME: We could probably have fileset be the union of ./. with flake.nix
## and flake.lock - I doubt we need anything else.
src = fileset.toSource {
fileset = config.pathToRoot;
root = config.pathToRoot;
};
## Some derivations will be different compared to target's initial state, so
## we'll need to be able to build something similar. Generally the derivation
## inputs aren't that different, so we use the initial state of the target as
## a base.
extraDependenciesFromMachine =
machine:
[
machine.system.build.toplevel.inputDerivation
machine.system.build.etc.inputDerivation
machine.system.build.etcBasedir.inputDerivation
machine.system.build.etcMetadataImage.inputDerivation
machine.system.build.extraUtils.inputDerivation
machine.system.path.inputDerivation
machine.system.build.setEnvironment.inputDerivation
machine.system.build.vm.inputDerivation
# machine.system.build.vmWithBootLoader.inputDerivation
machine.system.build.bootStage1.inputDerivation
machine.system.build.bootStage2.inputDerivation
]
++ concatLists (
lib.mapAttrsToList (
_k: v: if v ? source.inputDerivation then [ v.source.inputDerivation ] else [ ]
) machine.environment.etc
);
allNodesButFake = filter (m: m != "fake") (attrNames config.nodes);
in
{
options = {
targetMachines = mkOption { };
pathToRoot = mkOption { };
## TODO: sanity check that (pathToRoot + "/${pathFromRoot}" == ./.)
pathFromRoot = mkOption { };
## FIXME: I wish I could just use `testScript` but with something like
## `mkOrder` to put this module's string before something else.
extraTestScript = mkOption { };
};
config = {
nodes =
{
deployer =
{ pkgs, nodes, ... }:
{
virtualisation = {
## Memory use is expected to be dominated by the NixOS evaluation,
## which happens on the deployer.
memorySize = 32 * 1024; # FIXME: trim down - maybe make it an option
diskSize = 50 * 1024; # FIXME: trim down - maybe make it an option
cores = 8; # FIXME: trim down - maybe make it an option
};
nix.settings = {
substituters = lib.mkForce [ ];
hashed-mirrors = null;
connect-timeout = 1;
};
system.extraDependencies =
lib.attrValues sources
++ [
# pkgs
pkgs.stdenv
pkgs.stdenvNoCC
]
++ concatLists (map (tm: extraDependenciesFromMachine nodes.${tm}) (targetMachines ++ [ "fake" ]));
};
## A “fake” node that can will not be started or used in the test, but
## whose configuration will be dumped in the deployer's extra
## dependencies. This is an indirect (and more pleasant) way of giving
## the deployer the right derivations to build the desired services.
fake.imports = [ ../basic/minimalTarget.nix ];
}
// genAttrs targetMachines (_: {
imports = [ ../basic/minimalTarget.nix ];
users.users.root.openssh.authorizedKeys.keyFiles = [
(pathToRoot + "/${pathFromRoot}/deployer.pub")
];
});
testScript = ''
${forConcat allNodesButFake (n: ''
${n}.start()
'')}
${forConcat allNodesButFake (n: ''
${n}.wait_for_unit("multi-user.target")
'')}
with subtest("Unpacking"):
deployer.succeed("cp -r --no-preserve=mode ${src} work")
with subtest("Configure the network"):
${forConcat targetMachines (
tm:
let
targetNetworkJSON = hostPkgs.writeText "target-network.json" (
builtins.toJSON config.nodes.${tm}.system.build.networkConfig
);
in
''
deployer.copy_from_host("${targetNetworkJSON}", "/root/target-network.json")
deployer.succeed("mv /root/target-network.json work/${pathFromRoot}/${tm}-network.json")
''
)}
with subtest("Configure the deployer key"):
deployer.succeed("""mkdir -p ~/.ssh && ssh-keygen -t rsa -N "" -f ~/.ssh/id_rsa""")
deployer_key = deployer.succeed("cat ~/.ssh/id_rsa.pub").strip()
deployer.succeed(f"echo '{deployer_key}' > work/${pathFromRoot}/deployer.pub")
${forConcat targetMachines (tm: ''
${tm}.succeed(f"mkdir -p /root/.ssh && echo '{deployer_key}' >> /root/.ssh/authorized_keys")
'')}
with subtest("Configure the target host key"):
${forConcat targetMachines (tm: ''
host_key = ${tm}.succeed("ssh-keyscan ${tm} | grep -v '^#' | cut -f 2- -d ' ' | head -n 1")
deployer.succeed(f"echo '{host_key}' > work/${pathFromRoot}/${tm}_host_key.pub")
'')}
${config.extraTestScript}
'';
};
}