colmena/src/nix/eval.nix
2020-12-28 17:17:13 -08:00

167 lines
4.8 KiB
Nix

{ rawHive }:
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 = {};
};
defaultMeta = {
name = "hive";
description = "A Colmena Hive";
# Can be a path, a lambda, or an initialized Nixpkgs attrset
nixpkgs = <nixpkgs>;
# Per-node Nixpkgs overrides
# Keys are hostnames.
nodeNixpkgs = {};
};
# Colmena-specific options
#
# Largely compatible with NixOps/Morph.
deploymentOptions = { name, lib, ... }:
let
types = lib.types;
in {
options = {
deployment = {
targetHost = lib.mkOption {
description = ''
The target SSH node for deployment.
By default, the node's attribute name will be used.
If set to null, only local deployment will be supported.
'';
type = types.nullOr types.str;
default = name;
};
targetUser = lib.mkOption {
description = ''
The user to use to log into the remote node.
'';
type = types.str;
default = "root";
};
allowLocalDeployment = lib.mkOption {
description = ''
Allow the configuration to be applied locally on the host running
Colmena.
For local deployment to work, all of the following must be true:
- The node must be running NixOS.
- The node must have deployment.allowLocalDeployment set to true.
- The node's networking.hostName must match the hostname.
To apply the configurations locally, run `colmena apply-local`.
You can also set deployment.targetHost to null if the nost is not
accessible over SSH (only local deployment will be possible).
'';
type = types.bool;
default = false;
};
tags = lib.mkOption {
description = ''
A list of tags for the node.
Can be used to select a group of nodes for deployment.
'';
type = types.listOf types.str;
default = [];
};
};
};
};
userMeta = if rawHive ? meta then rawHive.meta
else if rawHive ? network then rawHive.network
else {};
# The final hive will always have the meta key instead of network.
hive = let
mergedHive = removeAttrs (defaultHive // rawHive) [ "meta" "network" ];
meta = {
meta = lib.recursiveUpdate defaultMeta userMeta;
};
in mergedHive // meta;
mkNixpkgs = configName: pkgConf:
if typeOf pkgConf == "path" then
import pkgConf {}
else if typeOf pkgConf == "lambda" then
pkgConf {}
else if typeOf pkgConf == "set" then
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
'';
pkgs = mkNixpkgs "meta.nixpkgs" (defaultMeta // userMeta).nixpkgs;
lib = pkgs.lib;
reservedNames = [ "defaults" "network" "meta" ];
evalNode = name: config: let
npkgs =
if hasAttr name hive.meta.nodeNixpkgs
then mkNixpkgs "meta.nodeNixpkgs.${name}" hive.meta.nodeNixpkgs.${name}
else pkgs;
evalConfig = import (npkgs.path + "/nixos/lib/eval-config.nix");
in evalConfig {
modules = [
deploymentOptions
hive.defaults
config
] ++ (import (npkgs.path + "/nixos/modules/module-list.nix"));
specialArgs = {
inherit name nodes;
modulesPath = npkgs.path + "/nixos/modules";
};
};
nodeNames = filter (name: ! elem name reservedNames) (attrNames hive);
# Exported attributes
#
# Functions are intended to be called with `nix-instantiate --eval --json`
nodes = listToAttrs (map (name: {
inherit name;
value = evalNode name hive.${name};
}) nodeNames);
deploymentConfigJson = toJSON (lib.attrsets.mapAttrs (name: eval: eval.config.deployment) nodes);
toplevel = lib.attrsets.mapAttrs (name: eval: eval.config.system.build.toplevel) nodes;
buildAll = buildSelected {
names = nodeNames;
};
buildSelected = { names ? null }: let
# Change in the order of the names should not cause a derivation to be created
selected = lib.attrsets.filterAttrs (name: _: elem name names) toplevel;
in derivation rec {
name = "colmena-${hive.meta.name}";
system = currentSystem;
json = toJSON (lib.attrsets.mapAttrs (k: v: toString v) selected);
builder = pkgs.writeScript "${name}.sh" ''
#!/bin/sh
echo "$json" > $out
'';
};
introspect = function: function {
inherit pkgs lib nodes;
};
in {
inherit nodes deploymentConfigJson toplevel buildAll buildSelected introspect;
}