Fediversity/deployment/check/basic/nixosTest.nix
Nicolas “Niols” Jeannerod f5db62e053 Add a basic integration test (#323)
This PR adds a basic deployment test to the repository. This test will, in a NixOS test, run a deployer VM and a target VM, and check that we manage to run `nixops4 apply` on the deployer VM to change things on the target VM. The ideas are all @roberth's and this test has been extremely heavily inspired by https://github.com/nixops4/nixops4-nixos/blob/main/test/default/nixosTest.nix.

Reviewed-on: Fediversity/Fediversity#323
Reviewed-by: Valentin Gagarin <valentin.gagarin@tweag.io>
Co-authored-by: Nicolas “Niols” Jeannerod <nicolas.jeannerod@moduscreate.com>
Co-committed-by: Nicolas “Niols” Jeannerod <nicolas.jeannerod@moduscreate.com>
2025-04-30 15:03:36 +02:00

161 lines
5.6 KiB
Nix

{
testers,
inputs,
runCommandNoCC,
nixops4-flake-in-a-bottle,
...
}:
testers.runNixOSTest (
{
lib,
config,
hostPkgs,
...
}:
let
vmSystem = config.node.pkgs.hostPlatform.system;
pathToRoot = ../../..;
pathFromRoot = "deployment/check/basic";
deploymentName = "check-deployment-basic";
## TODO: sanity check the existence of (pathToRoot + "/flake.nix")
## TODO: sanity check that (pathToRoot + "/${pathFromRoot}" == ./.)
## The whole repository, with the flake at its root.
src = lib.fileset.toSource {
fileset = pathToRoot;
root = pathToRoot;
};
## We will need to override some inputs by the empty flake, so we make one.
emptyFlake = runCommandNoCC "empty-flake" { } ''
mkdir $out
echo "{ outputs = { self }: {}; }" > $out/flake.nix
'';
targetNetworkJSON = hostPkgs.writeText "target-network.json" (
builtins.toJSON config.nodes.target.system.build.networkConfig
);
in
{
name = "deployment-basic";
imports = [
inputs.nixops4-nixos.modules.nixosTest.static
];
nodes = {
deployer =
{ pkgs, nodes, ... }:
{
environment.systemPackages = [
inputs.nixops4.packages.${vmSystem}.default
];
virtualisation = {
## Memory use is expected to be dominated by the NixOS evaluation,
## which happens on the deployer.
memorySize = 4096;
diskSize = 10 * 1024;
cores = 2;
};
nix.settings = {
substituters = lib.mkForce [ ];
hashed-mirrors = null;
connect-timeout = 1;
};
system.extraDependencies =
[
"${inputs.flake-parts}"
"${inputs.flake-parts.inputs.nixpkgs-lib}"
"${inputs.nixops4}"
"${inputs.nixops4-nixos}"
"${inputs.nixpkgs}"
pkgs.stdenv
pkgs.stdenvNoCC
pkgs.cowsay
pkgs.cowsay.inputDerivation # NOTE: Crucial!!!
## 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.
nodes.target.system.build.toplevel.inputDerivation
nodes.target.system.build.etc.inputDerivation
nodes.target.system.path.inputDerivation
nodes.target.system.build.bootStage1.inputDerivation
nodes.target.system.build.bootStage2.inputDerivation
]
++ lib.concatLists (
lib.mapAttrsToList (
_k: v: if v ? source.inputDerivation then [ v.source.inputDerivation ] else [ ]
) nodes.target.environment.etc
);
};
target.imports = [ ./minimalTarget.nix ];
};
testScript = ''
start_all()
target.wait_for_unit("multi-user.target")
deployer.wait_for_unit("multi-user.target")
with subtest("Unpacking"):
deployer.succeed("cp -r --no-preserve=mode ${src} work")
with subtest("Configure the network"):
deployer.copy_from_host("${targetNetworkJSON}", "/root/target-network.json")
deployer.succeed("mv /root/target-network.json work/${pathFromRoot}/target-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")
target.succeed(f"mkdir -p /root/.ssh && echo '{deployer_key}' >> /root/.ssh/authorized_keys")
with subtest("Configure the target host key"):
target_host_key = target.succeed("ssh-keyscan target | grep -v '^#' | cut -f 2- -d ' ' | head -n 1")
deployer.succeed(f"echo '{target_host_key}' > work/${pathFromRoot}/target_host_key.pub")
## NOTE: This is super slow. It could probably be optimised in Nix, for
## instance by allowing to grab things directly from the host's store.
with subtest("Override the lock"):
deployer.succeed("""
cd work
nix flake lock --extra-experimental-features 'flakes nix-command' \
--offline -v \
--override-input flake-parts ${inputs.flake-parts} \
--override-input nixops4 ${nixops4-flake-in-a-bottle} \
\
--override-input nixops4-nixos ${inputs.nixops4-nixos} \
--override-input nixops4-nixos/flake-parts ${inputs.nixops4-nixos.inputs.flake-parts} \
--override-input nixops4-nixos/flake-parts/nixpkgs-lib ${inputs.nixops4-nixos.inputs.flake-parts.inputs.nixpkgs-lib} \
--override-input nixops4-nixos/nixops4-nixos ${emptyFlake} \
--override-input nixops4-nixos/nixpkgs ${inputs.nixops4-nixos.inputs.nixpkgs} \
--override-input nixops4-nixos/nixops4 ${nixops4-flake-in-a-bottle} \
--override-input nixops4-nixos/git-hooks-nix ${emptyFlake} \
\
--override-input nixpkgs ${inputs.nixpkgs} \
--override-input git-hooks ${inputs.git-hooks} \
;
""")
with subtest("Check the status before deployment"):
target.fail("cowsay hi 1>&2")
with subtest("Run the deployment"):
deployer.succeed("cd work && nixops4 apply ${deploymentName} --show-trace --no-interactive")
with subtest("Check the deployment"):
target.succeed("cowsay hi 1>&2")
'';
}
)