{ inputs, lib, config, hostPkgs, sources, ... }: let inherit (builtins) concatStringsSep toJSON ; inherit (lib) types fileset mkOption genAttrs attrNames optionalString ; inherit (hostPkgs) writeText ; forConcat = xs: f: concatStringsSep "\n" (map f xs); in { _class = "nixosTest"; imports = [ ../common/sharedOptions.nix ]; options = { ## FIXME: I wish I could just use `testScript` but with something like ## `mkOrder` to put this module's string before something else. extraTestScript = mkOption { }; sourceFileset = mkOption { ## REVIEW: Upstream to nixpkgs? type = types.mkOptionType { name = "fileset"; description = "fileset"; descriptionClass = "noun"; check = (x: (builtins.tryEval (fileset.unions [ x ])).success); merge = (_: defs: fileset.unions (map (x: x.value) defs)); }; description = '' A fileset that will be copied to the deployer node in the current working directory. This should contain all the files that are necessary to run that particular test, such as the NixOS modules necessary to evaluate a deployment. ''; }; }; config = { sourceFileset = fileset.unions [ ../../../mkFlake.nix ../../../flake.lock ../../../npins ../../data-model.nix ../../function.nix ../common/sharedOptions.nix ../common/targetNode.nix ../common/targetResource.nix ]; acmeNodeIP = config.nodes.acme.networking.primaryIPAddress; nodes = { deployer = { imports = [ ../common/deployerNode.nix ]; _module.args = { inherit inputs sources; }; enableAcme = config.enableAcme; acmeNodeIP = config.nodes.acme.networking.primaryIPAddress; }; } // ( if config.enableAcme then { acme = { ## FIXME: This makes `nodes.acme` into a local resolver. Maybe this will ## break things once we play with DNS? imports = [ "${inputs.nixpkgs}/nixos/tests/common/acme/server" ]; ## We aren't testing ACME - we just want certificates. systemd.services.pebble.environment.PEBBLE_VA_ALWAYS_VALID = "1"; }; } else { } ) // genAttrs config.targetMachines (_: { imports = [ ../common/targetNode.nix ]; _module.args = { inherit inputs sources; }; enableAcme = config.enableAcme; acmeNodeIP = if config.enableAcme then config.nodes.acme.networking.primaryIPAddress else null; }); testScript = '' ${forConcat (attrNames config.nodes) (n: '' ${n}.start() '')} ${forConcat (attrNames config.nodes) (n: '' ${n}.wait_for_unit("multi-user.target") '')} ## A subset of the repository that is necessary for this test. It will be ## copied inside the test. The smaller this set, the faster our CI, because we ## won't need to re-run when things change outside of it. with subtest("Unpacking"): deployer.succeed("cp -r --no-preserve=mode ${ fileset.toSource { root = ../../..; fileset = config.sourceFileset; } }/* .") with subtest("Configure the network"): ${forConcat config.targetMachines ( tm: let targetNetworkJSON = writeText "target-network.json" ( toJSON config.nodes.${tm}.system.build.networkConfig ); in '' deployer.copy_from_host("${targetNetworkJSON}", "${config.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() ${forConcat config.targetMachines (tm: '' ${tm}.succeed(f"mkdir -p /root/.ssh && echo '{deployer_key}' >> /root/.ssh/authorized_keys") '')} with subtest("Configure the target host key"): ${forConcat config.targetMachines (tm: '' host_key = ${tm}.succeed("ssh-keyscan ${tm} | grep -v '^#' | cut -f 2- -d ' ' | head -n 1") deployer.succeed(f"echo '{host_key}' > ${config.pathFromRoot}/${tm}_host_key.pub") '')} # with subtest("Override the flake and its lock"): # deployer.succeed("cp ${config.pathFromRoot}/flake-under-test.nix flake.nix") ${optionalString config.enableAcme '' with subtest("Set up handmade DNS"): deployer.succeed("echo '${config.nodes.acme.networking.primaryIPAddress}' > ${config.pathFromRoot}/acme_server_ip") ''} ${config.extraTestScript} ''; }; }