colmena/src/nix/hive/eval.nix
oddlama c6f42e447d fix: allow specialArgs and nodeSpecialArgs to override name and nodes
Co-authored-by: Zhaofeng Li <hello@zhaofeng.li>
2023-07-01 09:07:17 -06:00

194 lines
6.8 KiB
Nix

{ rawHive ? null # Colmena Hive attrset
, rawFlake ? null # Nix Flake attrset with `outputs.colmena`
, hermetic ? rawFlake != null # Whether we are allowed to use <nixpkgs>
, colmenaOptions
, colmenaModules
}:
with builtins;
let
defaultHive = {
# Will be set in defaultHiveMeta
meta = {};
# Like in NixOps, there is a special host named `defaults`
# containing configurations that will be applied to all
# hosts.
defaults = {};
};
uncheckedHive = let
flakeToHive = rawFlake:
if rawFlake.outputs ? colmena then rawFlake.outputs.colmena else throw "Flake must define outputs.colmena.";
rawToHive = rawHive:
if typeOf rawHive == "lambda" || rawHive ? __functor then rawHive {}
else if typeOf rawHive == "set" then rawHive
else throw "The config must evaluate to an attribute set.";
in
if rawHive != null then rawToHive rawHive
else if rawFlake != null then flakeToHive rawFlake
else throw "Either a plain Hive attribute set or a Nix Flake attribute set must be specified.";
uncheckedUserMeta =
if uncheckedHive ? meta && uncheckedHive ? network then
throw "Only one of `network` and `meta` may be specified. `meta` should be used as `network` is for NixOps compatibility."
else if uncheckedHive ? meta then uncheckedHive.meta
else if uncheckedHive ? network then uncheckedHive.network
else {};
# The final hive will always have the meta key instead of network.
hive = let
userMeta = (lib.modules.evalModules {
modules = [ colmenaOptions.metaOptions uncheckedUserMeta ];
}).config;
mergedHive =
assert lib.assertMsg (!(uncheckedHive ? __schema)) ''
You cannot pass in an already-evaluated Hive into the evaluator.
Hint: Use the `colmenaHive` output instead of `colmena`.
'';
removeAttrs (defaultHive // uncheckedHive) [ "meta" "network" ];
meta = {
meta =
if !hermetic && userMeta.nixpkgs == null
then userMeta // { nixpkgs = <nixpkgs>; }
else userMeta;
};
in mergedHive // meta;
configsFor = node: let
nodeConfig = hive.${node};
in
assert lib.assertMsg (!elem node reservedNames) "\"${node}\" is a reserved name and cannot be used as the name of a node";
if typeOf nodeConfig == "list" then nodeConfig
else [ nodeConfig ];
mkNixpkgs = configName: pkgConf: let
uninitializedError = typ: ''
Passing ${typ} as ${configName} is no longer accepted with Flakes.
Please initialize Nixpkgs like the following:
{
# ...
outputs = { nixpkgs, ... }: {
colmena = {
${configName} = import nixpkgs {
system = "x86_64-linux"; # Set your desired system here
overlays = [];
};
};
};
}
'';
in
if typeOf pkgConf == "path" || (typeOf pkgConf == "set" && pkgConf ? outPath) then
if hermetic then throw (uninitializedError "a path to Nixpkgs")
# The referenced file might return an initialized Nixpkgs attribute set directly
else mkNixpkgs configName (import pkgConf)
else if typeOf pkgConf == "lambda" then
if hermetic then throw (uninitializedError "a Nixpkgs lambda")
else pkgConf { overlays = []; }
else if typeOf pkgConf == "set" then
if pkgConf ? outputs then throw (uninitializedError "an uninitialized Nixpkgs input")
else pkgConf
else throw ''
${configName} must be one of:
- A path to Nixpkgs (e.g., <nixpkgs>)
- A Nixpkgs lambda (e.g., import <nixpkgs>)
- A Nixpkgs attribute set
'';
nixpkgs = let
# Can't rely on the module system yet
nixpkgsConf =
if uncheckedUserMeta ? nixpkgs then uncheckedUserMeta.nixpkgs
else if hermetic then throw "meta.nixpkgs must be specified in hermetic mode."
else <nixpkgs>;
in mkNixpkgs "meta.nixpkgs" nixpkgsConf;
lib = nixpkgs.lib;
reservedNames = [ "defaults" "network" "meta" ];
evalNode = name: configs: let
npkgs =
if hasAttr name hive.meta.nodeNixpkgs
then mkNixpkgs "meta.nodeNixpkgs.${name}" hive.meta.nodeNixpkgs.${name}
else nixpkgs;
evalConfig = import (npkgs.path + "/nixos/lib/eval-config.nix");
# Here we need to merge the configurations in meta.nixpkgs
# and in machine config.
nixpkgsModule = { config, lib, ... }: let
hasTypedConfig = lib.versionAtLeast lib.version "22.11pre";
in {
nixpkgs.overlays = lib.mkBefore npkgs.overlays;
nixpkgs.config = if hasTypedConfig then lib.mkBefore npkgs.config else lib.mkOptionDefault npkgs.config;
warnings = let
# Before 22.11, most config keys were untyped thus the merging
# was broken. Let's warn the user if not all config attributes
# set in meta.nixpkgs are overridden.
metaKeys = attrNames npkgs.config;
nodeKeys = [ "doCheckByDefault" "warnings" "allowAliases" ] ++ (attrNames config.nixpkgs.config);
remainingKeys = filter (k: ! elem k nodeKeys) metaKeys;
in
lib.optional (!hasTypedConfig && length remainingKeys != 0)
"The following Nixpkgs configuration keys set in meta.nixpkgs will be ignored: ${toString remainingKeys}";
};
in evalConfig {
inherit (npkgs) system;
modules = [
nixpkgsModule
colmenaModules.assertionModule
colmenaModules.keyChownModule
colmenaModules.keyServiceModule
colmenaOptions.deploymentOptions
hive.defaults
] ++ configs;
specialArgs = {
inherit name;
nodes = uncheckedNodes;
} // hive.meta.specialArgs // (hive.meta.nodeSpecialArgs.${name} or {});
};
nodeNames = filter (name: ! elem name reservedNames) (attrNames hive);
# Used as the `nodes` argument in modules. We skip recursive type checking
# for performance.
uncheckedNodes = listToAttrs (map (name: let
configs = [
{
_module.check = false;
}
] ++ configsFor name;
in {
inherit name;
value = evalNode name configs;
}) nodeNames);
# Add required config Key here since we don't want to eval nixpkgs
metaConfigKeys = [
"name" "description"
"machinesFile"
"allowApplyAll"
];
in rec {
# Exported attributes
__schema = "v0";
nodes = listToAttrs (map (name: { inherit name; value = evalNode name (configsFor name); }) nodeNames);
toplevel = lib.mapAttrs (_: v: v.config.system.build.toplevel) nodes;
deploymentConfig = lib.mapAttrs (_: v: v.config.deployment) nodes;
deploymentConfigSelected = names: lib.filterAttrs (name: _: elem name names) deploymentConfig;
evalSelected = names: lib.filterAttrs (name: _: elem name names) toplevel;
evalSelectedDrvPaths = names: lib.mapAttrs (_: v: v.drvPath) (evalSelected names);
metaConfig = lib.filterAttrs (n: v: elem n metaConfigKeys) hive.meta;
introspect = f: f { inherit lib; pkgs = nixpkgs; nodes = uncheckedNodes; };
}