diff --git a/.gitignore b/.gitignore index b6baada..578cc4d 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,4 @@ # Generated by nix-pre-commit-hooks /.pre-commit-config.yaml +.nixos-test-history diff --git a/data/eval-machines.nix b/data/eval-machines.nix index 6c342e1..24450cd 100644 --- a/data/eval-machines.nix +++ b/data/eval-machines.nix @@ -10,10 +10,11 @@ let runCommand = network.network.runCommand or nwPkgs.runCommand or (import { }).runCommand; -in with lib; +in +with lib; let - defaults = network.defaults or {}; + defaults = network.defaults or { }; modules = { machineName, nodes, check }: [ @@ -58,36 +59,41 @@ let "_file" ]); -in rec { +in +rec { # Unchecked configuration of all machines. # Using unchecked config evaluation allows each machine to access other machines # configuration without recursing as full evaluation is prevented - uncheckedNodes = listToAttrs (map (machineName: { - name = machineName; - value = import evalConfig { - # Force decide system in module system - system = null; - modules = modules { - inherit machineName; - check = false; - nodes = uncheckedNodes; + uncheckedNodes = listToAttrs (map + (machineName: { + name = machineName; + value = import evalConfig { + # Force decide system in module system + system = null; + modules = modules { + inherit machineName; + check = false; + nodes = uncheckedNodes; + }; }; - }; - }) machineNames); + }) + machineNames); # Compute the definitions of the machines. - nodes = listToAttrs (map (machineName: { - name = machineName; - value = import evalConfig { - # Force decide system in module system - system = null; - modules = modules { - inherit machineName; - check = true; - nodes = uncheckedNodes; + nodes = listToAttrs (map + (machineName: { + name = machineName; + value = import evalConfig { + # Force decide system in module system + system = null; + modules = modules { + inherit machineName; + check = true; + nodes = uncheckedNodes; + }; }; - }; - }) machineNames); + }) + machineNames); deploymentInfoModule = { deployment = { @@ -98,58 +104,62 @@ in rec { }; # Phase 1: evaluate only the deployment attributes. - info = let network' = network; - in rec { - - machines = flip mapAttrs nodes (n: v': - let v = scrubOptionValue v'; - in { - inherit (v.config.deployment) - targetHost targetPort targetUser secrets healthChecks buildOnly - substituteOnDestination tags; - name = n; - nixosRelease = v.config.system.nixos.release or (removeSuffix - v.config.system.nixos.version.suffix v.config.system.nixos.version); - nixConfig = mapAttrs (n: v: - if builtins.isString v then - v - else - throw "nix option '${n}' must have a string typed value") - (network'.network.nixConfig or { }); - }); - - machineList = map (key: getAttr key machines) (attrNames machines); - network = network'.network or { }; - deployment = { - hosts = machineList; - meta = { - description = network.description or ""; - ordering = network.ordering or { }; + info = + let network' = network; + in rec { + + machines = flip mapAttrs nodes (n: v': + let v = scrubOptionValue v'; + in { + inherit (v.config.deployment) + targetHost targetPort targetUser secrets healthChecks buildOnly + substituteOnDestination tags; + name = n; + nixosRelease = v.config.system.nixos.release or (removeSuffix + v.config.system.nixos.version.suffix + v.config.system.nixos.version); + nixConfig = mapAttrs + (n: v: + if builtins.isString v then + v + else + throw "nix option '${n}' must have a string typed value") + (network'.network.nixConfig or { }); + }); + + machineList = map (key: getAttr key machines) (attrNames machines); + network = network'.network or { }; + deployment = { + hosts = machineList; + meta = { + description = network.description or ""; + ordering = network.ordering or { }; + }; }; - }; - buildShell = network.buildShell.drvPath or null; - }; + buildShell = network.buildShell.drvPath or null; + }; # Phase 2: build complete machine configurations. machines = { argsFile, buildTargets ? null }: let fileArgs = builtins.fromJSON (builtins.readFile argsFile); nodes' = filterAttrs (n: _v: elem n fileArgs.Names) nodes; - in runCommand "morph" { preferLocalBuild = true; } - (if buildTargets == null then '' - mkdir -p $out - ${toString (mapAttrsToList (nodeName: nodeDef: '' - ln -s ${nodeDef.config.system.build.toplevel} $out/${nodeName} - '') nodes')} - '' else '' - mkdir -p $out - ${toString (mapAttrsToList (nodeName: nodeDef: '' - mkdir -p $out/${nodeName} - ${toString (mapAttrsToList (buildName: buildFn: '' - ln -s ${buildFn nodeDef} $out/${nodeName}/${buildName} - '') buildTargets)} - '') nodes')} - ''); + in + runCommand "morph" { preferLocalBuild = true; } + (if buildTargets == null then '' + mkdir -p $out + ${toString (mapAttrsToList (nodeName: nodeDef: '' + ln -s ${nodeDef.config.system.build.toplevel} $out/${nodeName} + '') nodes')} + '' else '' + mkdir -p $out + ${toString (mapAttrsToList (nodeName: nodeDef: '' + mkdir -p $out/${nodeName} + ${toString (mapAttrsToList (buildName: buildFn: '' + ln -s ${buildFn nodeDef} $out/${nodeName}/${buildName} + '') buildTargets)} + '') nodes')} + ''); } diff --git a/data/options.nix b/data/options.nix index 64f09fa..7e0bcf8 100644 --- a/data/options.nix +++ b/data/options.nix @@ -165,7 +165,8 @@ let }; }); -in { +in +{ options.deployment = { targetHost = mkOption { diff --git a/flake.nix b/flake.nix index 666a04a..10a09af 100644 --- a/flake.nix +++ b/flake.nix @@ -29,26 +29,30 @@ # Eval the treefmt modules from ./treefmt.nix treefmtEval = treefmt-nix.lib.evalModule pkgs ./treefmt.nix; - in { + in + rec { # for `nix fmt` formatter = treefmtEval.config.build.wrapper; # for `nix flake check` checks = { + vm_integration_tests = pkgs.callPackage ./nixos/tests/integration_tests.nix { inherit packages; }; formatting = treefmtEval.config.build.check self; build = self.packages.${system}.morph; - pre-commit-check = let - # some treefmt formatters are not supported in pre-commit-hooks we - # filter them out for now. - toFilter = [ "yamlfmt" ]; - filterFn = n: _v: (!builtins.elem n toFilter); - treefmtFormatters = - pkgs.lib.mapAttrs (_n: v: { inherit (v) enable; }) - (pkgs.lib.filterAttrs filterFn (import ./treefmt.nix).programs); - in pre-commit-hooks.lib.${system}.run { - src = ./.; - hooks = treefmtFormatters; - }; + pre-commit-check = + let + # some treefmt formatters are not supported in pre-commit-hooks we + # filter them out for now. + toFilter = [ "yamlfmt" ]; + filterFn = n: _v: (!builtins.elem n toFilter); + treefmtFormatters = + pkgs.lib.mapAttrs (_n: v: { inherit (v) enable; }) + (pkgs.lib.filterAttrs filterFn (import ./treefmt.nix).programs); + in + pre-commit-hooks.lib.${system}.run { + src = ./.; + hooks = treefmtFormatters; + }; }; # Acessible through 'nix develop' or 'nix-shell' (legacy) diff --git a/nixos/tests/integration_tests.nix b/nixos/tests/integration_tests.nix new file mode 100644 index 0000000..69d211d --- /dev/null +++ b/nixos/tests/integration_tests.nix @@ -0,0 +1,32 @@ +{ nixosTest, packages, ... }: + + +nixosTest { + name = "morph-deployment-test"; + nodes = + let + boot.loader = { + systemd-boot.enable = true; + efi.canTouchEfiVariables = true; + }; + services.openssh = { + enable = true; + startWhenNeeded = false; + }; + in + { + deployer = _: { + inherit services boot; + environment.systemPackages = [ packages.morph ]; + }; + target = _: { + inherit services boot; + }; + }; + testScript = '' + start_all() + + deployer.wait_for_unit("sshd") + target.wait_for_unit("sshd") + ''; +} diff --git a/nixpkgs.nix b/nixpkgs.nix index 461a15a..364108e 100644 --- a/nixpkgs.nix +++ b/nixpkgs.nix @@ -1,3 +1,3 @@ # Intentionally impure for CI against nixos-unstable builtins.fetchTarball -"https://github.com/NixOS/nixpkgs/archive/nixos-unstable.tar.gz" + "https://github.com/NixOS/nixpkgs/archive/nixos-unstable.tar.gz" diff --git a/treefmt.nix b/treefmt.nix index 6789b19..e4f1216 100644 --- a/treefmt.nix +++ b/treefmt.nix @@ -1,7 +1,7 @@ { projectRootFile = "flake.nix"; programs = { - nixfmt.enable = true; # nix formatter + nixpkgs-fmt.enable = true; # nix formatter statix.enable = true; # nix static analysis deadnix.enable = true; # find dead nix code shellcheck.enable = true; # bash/shell @@ -11,7 +11,7 @@ }; settings = { formatter = { - nixfmt.includes = [ "*.nix" "./data/*" ]; + nixpkgs-fmt.includes = [ "*.nix" "./data/*" ]; statix.includes = [ "*.nix" "./data/*" ]; deadnix.includes = [ "*.nix" "./data/*" ]; };