{ 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} ''; }; }