Inventory: init module merge & validation logic for inventory
This commit is contained in:
committed by
hsjobeki
parent
eb221244e6
commit
0d4928ab73
@@ -31,59 +31,62 @@
|
||||
inventory ? { },
|
||||
}:
|
||||
let
|
||||
_inventory =
|
||||
(
|
||||
if services != { } && inventory == { } then
|
||||
{ services = lib.mapAttrs (_name: value: { default = value; }) services; }
|
||||
else if services == { } && inventory != { } then
|
||||
# Internal inventory, this is the result of merging all potential inventory sources:
|
||||
# - Default instances configured via 'services'
|
||||
# - The inventory overrides
|
||||
# - Machines that exist in inventory.machines
|
||||
# - Machines explicitly configured via 'machines' argument
|
||||
# - Machines that exist in the machines directory
|
||||
# Checks on the module level:
|
||||
# - Each service role must reference a valid machine after all machines are merged
|
||||
mergedInventory =
|
||||
(lib.evalModules {
|
||||
modules = [
|
||||
./interface.nix
|
||||
{ inherit meta; }
|
||||
# Default instances configured via 'services'
|
||||
{
|
||||
services = lib.mapAttrs (_name: value: {
|
||||
default = value // {
|
||||
meta.name = lib.mkDefault _name;
|
||||
};
|
||||
}) services;
|
||||
}
|
||||
# The inventory overrides
|
||||
inventory
|
||||
else if services != { } && inventory != { } then
|
||||
throw "Either services or inventory should be set, but not both."
|
||||
else
|
||||
{ }
|
||||
)
|
||||
// {
|
||||
machines = lib.mapAttrs (
|
||||
name: config:
|
||||
(lib.attrByPath [
|
||||
"clan"
|
||||
"meta"
|
||||
] { } config)
|
||||
// {
|
||||
name = (
|
||||
lib.attrByPath [
|
||||
# Machines explicitly configured via 'machines' argument
|
||||
{
|
||||
# { ${name} :: meta // { name, tags } }
|
||||
machines = lib.mapAttrs (
|
||||
name: config:
|
||||
(lib.attrByPath [
|
||||
"clan"
|
||||
"meta"
|
||||
"name"
|
||||
] name config
|
||||
);
|
||||
tags = lib.attrByPath [
|
||||
"clan"
|
||||
"tags"
|
||||
] [ ] config;
|
||||
] { } config)
|
||||
// {
|
||||
# meta.name default is the attribute name of the machine
|
||||
name = lib.mkDefault (
|
||||
lib.attrByPath [
|
||||
"clan"
|
||||
"meta"
|
||||
"name"
|
||||
] name config
|
||||
);
|
||||
tags = lib.attrByPath [
|
||||
"clan"
|
||||
"tags"
|
||||
] [ ] config;
|
||||
}
|
||||
) machines;
|
||||
}
|
||||
) machines;
|
||||
};
|
||||
# Machines that exist in the machines directory
|
||||
{ machines = lib.mapAttrs (name: _: { inherit name; }) machinesDirs; }
|
||||
];
|
||||
}).config;
|
||||
|
||||
buildInventory = import ./inventory.nix { inherit lib clan-core; };
|
||||
|
||||
pkgs = import nixpkgs { };
|
||||
|
||||
inventoryFile = builtins.toFile "inventory.json" (builtins.toJSON _inventory);
|
||||
|
||||
# a Derivation that can be forced to validate the inventory
|
||||
# It is not used directly here.
|
||||
validatedFile = pkgs.stdenv.mkDerivation {
|
||||
name = "validated-inventory";
|
||||
src = ../../inventory/src;
|
||||
buildInputs = [ pkgs.cue ];
|
||||
installPhase = ''
|
||||
cue vet ${inventoryFile} root.cue -d "#Root"
|
||||
cp ${inventoryFile} $out
|
||||
'';
|
||||
};
|
||||
|
||||
serviceConfigs = buildInventory _inventory;
|
||||
serviceConfigs = buildInventory mergedInventory;
|
||||
|
||||
deprecationWarnings = [
|
||||
(lib.warnIf (
|
||||
@@ -167,6 +170,8 @@ let
|
||||
clan-core.nixosModules.clanCore
|
||||
extraConfig
|
||||
(machines.${name} or { })
|
||||
# Inherit the inventory assertions ?
|
||||
{ inherit (mergedInventory) assertions; }
|
||||
{ imports = serviceConfigs.${name} or { }; }
|
||||
(
|
||||
{
|
||||
@@ -250,7 +255,7 @@ builtins.deepSeq deprecationWarnings {
|
||||
# Evaluated clan meta
|
||||
# Merged /clan/meta.json with overrides from buildClan
|
||||
meta = mergedMeta;
|
||||
inherit _inventory validatedFile;
|
||||
inventory = mergedInventory;
|
||||
|
||||
# machine specifics
|
||||
machines = configsPerSystem;
|
||||
|
||||
120
lib/build-clan/interface.nix
Normal file
120
lib/build-clan/interface.nix
Normal file
@@ -0,0 +1,120 @@
|
||||
{ config, lib, ... }:
|
||||
let
|
||||
t = lib.types;
|
||||
|
||||
metaOptions = {
|
||||
name = lib.mkOption { type = t.str; };
|
||||
description = lib.mkOption {
|
||||
default = null;
|
||||
type = t.nullOr t.str;
|
||||
};
|
||||
icon = lib.mkOption {
|
||||
default = null;
|
||||
type = t.nullOr t.str;
|
||||
};
|
||||
};
|
||||
|
||||
machineRef = lib.mkOptionType {
|
||||
name = "machineRef";
|
||||
description = "Machine :: [${builtins.concatStringsSep " | " (builtins.attrNames config.machines)}]";
|
||||
check = v: lib.isString v && builtins.elem v (builtins.attrNames config.machines);
|
||||
merge = lib.mergeEqualOption;
|
||||
};
|
||||
|
||||
allTags = lib.unique (
|
||||
lib.foldlAttrs (
|
||||
tags: _: m:
|
||||
tags ++ m.tags or [ ]
|
||||
) [ ] config.machines
|
||||
);
|
||||
|
||||
tagRef = lib.mkOptionType {
|
||||
name = "tagRef";
|
||||
description = "Tags :: [${builtins.concatStringsSep " | " allTags}]";
|
||||
check = v: lib.isString v && builtins.elem v allTags;
|
||||
merge = lib.mergeEqualOption;
|
||||
};
|
||||
in
|
||||
{
|
||||
options.assertions = lib.mkOption {
|
||||
type = t.listOf t.unspecified;
|
||||
internal = true;
|
||||
default = [ ];
|
||||
};
|
||||
config.assertions = lib.foldlAttrs (
|
||||
ass1: serviceName: c:
|
||||
ass1
|
||||
++ lib.foldlAttrs (
|
||||
ass2: instanceName: instanceConfig:
|
||||
let
|
||||
serviceMachineNames = lib.attrNames instanceConfig.machines;
|
||||
topLevelMachines = lib.attrNames config.machines;
|
||||
# All machines must be defined in the top-level machines
|
||||
assertions = builtins.map (m: {
|
||||
assertion = builtins.elem m topLevelMachines;
|
||||
message = "${serviceName}.${instanceName}.machines.${m}. Should be one of [ ${builtins.concatStringsSep " | " topLevelMachines} ]";
|
||||
}) serviceMachineNames;
|
||||
in
|
||||
ass2 ++ assertions
|
||||
) [ ] c
|
||||
) [ ] config.services;
|
||||
|
||||
options.meta = metaOptions;
|
||||
|
||||
options.machines = lib.mkOption {
|
||||
default = { };
|
||||
type = t.attrsOf (
|
||||
t.submodule {
|
||||
options = {
|
||||
inherit (metaOptions) name description icon;
|
||||
tags = lib.mkOption {
|
||||
default = [ ];
|
||||
apply = lib.unique;
|
||||
type = t.listOf t.str;
|
||||
};
|
||||
};
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
options.services = lib.mkOption {
|
||||
type = t.attrsOf (
|
||||
t.attrsOf (
|
||||
t.submodule {
|
||||
options.meta = metaOptions;
|
||||
options.config = lib.mkOption {
|
||||
default = { };
|
||||
type = t.anything;
|
||||
};
|
||||
options.machines = lib.mkOption {
|
||||
default = { };
|
||||
type = t.attrsOf (
|
||||
t.submodule {
|
||||
options.config = lib.mkOption {
|
||||
default = { };
|
||||
type = t.anything;
|
||||
};
|
||||
}
|
||||
);
|
||||
};
|
||||
options.roles = lib.mkOption {
|
||||
default = { };
|
||||
type = t.attrsOf (
|
||||
t.submodule {
|
||||
options.machines = lib.mkOption {
|
||||
default = [ ];
|
||||
type = t.listOf machineRef;
|
||||
};
|
||||
options.tags = lib.mkOption {
|
||||
default = [ ];
|
||||
apply = lib.unique;
|
||||
type = t.listOf tagRef;
|
||||
};
|
||||
}
|
||||
);
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -90,7 +90,7 @@ let
|
||||
];
|
||||
}
|
||||
{
|
||||
config.clan.services.${moduleName}.${instanceName} = {
|
||||
config.clan.inventory.services.${moduleName}.${instanceName} = {
|
||||
roles = resolvedRoles;
|
||||
# TODO: Add inverseRoles to the service config if needed
|
||||
# inherit inverseRoles;
|
||||
|
||||
Reference in New Issue
Block a user