metanix/lib/addressing/default.nix
2026-05-01 13:07:30 -05:00

516 lines
14 KiB
Nix

{ lib, ... }:
let
inherit (lib)
assertMsg
attrNames
concatMapAttrs
concatStringsSep
filterAttrs
listToAttrs
mapAttrs'
nameValuePair
optionalAttrs
imap1
imap0
length
filter
findFirst
toLower
unique;
mkInitials =
name:
let
lower = toLower name;
noApos = builtins.replaceStrings [ "'" ] [ " " ] lower;
words = filter (w: w != "") (builtins.split " +" noApos);
letters = concatStringsSep "" (builtins.map (w: builtins.substring 0 1 w) words);
in
if letters == "" then "loc" else builtins.substring 0 3 letters;
mkLocationCode =
name:
let
base = mkInitials name;
hash = builtins.substring 0 4 (builtins.hashString "sha256" name);
in
"${base}-${hash}";
# Manual elemIndex so we do not care what nixpkgs version you are on.
elemIndex =
name: list:
let
idxs = imap0 (i: v: if v == name then i else null) list;
in
findFirst (i: i != null) null idxs;
subnetTypes = {
dmz = 0;
main = 1;
guest = 2;
iot = 3;
storage = 4;
management = 5;
infra = 6;
lab = 7;
};
roles = {
coreServer = 0;
router = 1;
infraDevice = 2;
server = 3;
workstation = 4;
thinClient = 5;
printer = 6;
nas = 7;
phone = 8;
camera = 9;
mobile = 10;
guestClient = 11;
appliance = 12;
automation = 13;
oobMgmt = 14;
hypervisor = 15;
containerHost = 16;
monitoring = 17;
logging = 18;
identity = 19;
labDevice = 20;
adminWorkstation = 21;
repoMirror = 22;
ci = 23;
media = 24;
homeAutomation = 25;
pool = 31;
};
subnetSlugs = {
dmz = "dmz";
main = "main";
guest = "guest";
iot = "iot";
storage = "storage";
management = "mgmt";
infra = "infra";
lab = "lab";
};
roleSlugs = {
coreServer = "core";
router = "router";
infraDevice = "infra-dev";
server = "server";
workstation = "ws";
thinClient = "thin";
printer = "printer";
nas = "nas";
phone = "phone";
camera = "camera";
mobile = "mobile";
guestClient = "guest";
appliance = "appliance";
automation = "automation";
oobMgmt = "oobm";
hypervisor = "hypervisor";
containerHost = "container";
monitoring = "monitoring";
logging = "logging";
identity = "identity";
labDevice = "lab-dev";
adminWorkstation = "admin-ws";
repoMirror = "mirror";
ci = "ci";
media = "media";
homeAutomation = "home-auto";
pool = "dhcp-pool";
};
thirdOctet =
{ type, role }:
let
_ = assertMsg (type >= 0 && type <= 7)
"metatron-addressing: type must be in 0..7, got ${toString type}";
__ = assertMsg (role >= 0 && role <= 31)
"metatron-addressing: role must be in 0..31, got ${toString role}";
in
type * 32 + role;
decodeThirdOctet =
octet:
let
_ = assertMsg (octet >= 0 && octet <= 255)
"metatron-addressing: octet must be in 0..255, got ${toString octet}";
type = builtins.div octet 32;
role = octet - (type * 32);
in
{ inherit type role; };
mkIp =
{ location, type, role, host }:
let
_ = assertMsg (location >= 0 && location <= 255)
"metatron-addressing: location must be in 0..255, got ${toString location}";
__ = assertMsg (host >= 0 && host <= 255)
"metatron-addressing: host must be in 0..255, got ${toString host}";
oct3 = thirdOctet { inherit type role; };
in
"10.${toString location}.${toString oct3}.${toString host}";
mkRoleRange =
{ location, type, role }:
let
_ = assertMsg (location >= 0 && location <= 255)
"metatron-addressing: location must be in 0..255, got ${toString location}";
__ = assertMsg (type >= 0 && type <= 7)
"metatron-addressing: type must be in 0..7, got ${toString type}";
___ = assertMsg (role >= 0 && role <= 31)
"metatron-addressing: role must be in 0..31, got ${toString role}";
oct3 = thirdOctet { inherit type role; };
base = "10.${toString location}.${toString oct3}";
in
{
start = "${base}.0";
end = "${base}.255";
};
mkRoleRangeNamed =
{ location, typeName, roleName }:
let
_ = assertMsg (builtins.hasAttr typeName subnetTypes)
"metatron-addressing: unknown subnet type '${typeName}'";
__ = assertMsg (builtins.hasAttr roleName roles)
"metatron-addressing: unknown role '${roleName}'";
type = subnetTypes.${typeName};
role = roles.${roleName};
in
mkRoleRange { inherit location type role; };
mkIpNamed =
{ location, typeName, roleName, host }:
let
_ = assertMsg (builtins.hasAttr typeName subnetTypes)
"metatron-addressing: unknown subnet type '${typeName}'";
__ = assertMsg (builtins.hasAttr roleName roles)
"metatron-addressing: unknown role '${roleName}'";
type = subnetTypes.${typeName};
role = roles.${roleName};
in
mkIp { inherit location host type role; };
mkSubnet =
{ location, type }:
let
_ = assertMsg (location >= 0 && location <= 255)
"metatron-addressing: location must be in 0..255, got ${toString location}";
__ = assertMsg (type >= 0 && type <= 7)
"metatron-addressing: type must be in 0..7, got ${toString type}";
oct3 = type * 32;
in
"10.${toString location}.${toString oct3}.0/19";
mkSubnetNamed =
{ location, typeName }:
let
_ = assertMsg (builtins.hasAttr typeName subnetTypes)
"metatron-addressing: unknown subnet type '${typeName}'";
type = subnetTypes.${typeName};
in
mkSubnet { inherit location type; };
mkVlan =
{ location, type }:
let
_ = assertMsg (location >= 0 && location <= 255)
"metatron-addressing: location must be in 0..255, got ${toString location}";
__ = assertMsg (type >= 0 && type <= 7)
"metatron-addressing: type must be in 0..7, got ${toString type}";
in
(location * 8) + type + 2;
mkVlanNamed =
{ location, typeName }:
let
_ = assertMsg (builtins.hasAttr typeName subnetTypes)
"metatron-addressing: unknown subnet type '${typeName}'";
type = subnetTypes.${typeName};
in
mkVlan { inherit location type; };
asList = value:
if value == null then [ ]
else if builtins.isList value then value
else [ value ];
scopeFromCfg = cfg: {
owners =
asList (cfg.owners or null)
++ asList (cfg.owner or null);
admins =
asList (cfg.admins or null)
++ asList (cfg.admin or null);
users = asList (cfg.users or null);
domain = cfg.domain or null;
};
mergeScopes = parent: child:
let
c = scopeFromCfg child;
in
{
owners = unique (parent.owners ++ c.owners);
admins = unique (parent.admins ++ c.admins);
users = unique (parent.users ++ c.users);
domain = if c.domain != null then c.domain else parent.domain;
};
emptyScope = {
owners = [ ];
admins = [ ];
users = [ ];
domain = null;
};
materializeScope = scope:
{
inherit (scope) owners admins users;
}
// optionalAttrs (scope.domain != null) {
inherit (scope) domain;
};
# locationName -> numeric id (0, 1, 2, ...)
locationIdForSpec =
spec:
let
locations = spec.locations or (throw "metatron-addressing: spec.locations is required");
locationNames = attrNames locations;
in
name:
let
idx = elemIndex name locationNames;
in
if idx == null then
throw "metatron-addressing: unknown location '${name}'"
else
idx;
hostRole = path: hostCfg:
hostCfg.role or (throw "metatron-addressing: host '${path}' is missing required field 'role' for address generation");
hostMapForSubnet =
{ loc, locCode, locationName, networkName, typeName, subnetCfg, scope }:
let
hostsForSubnet = subnetCfg.hosts or { };
hostNames = filter (hn: builtins.substring 0 1 hn != "_") (attrNames hostsForSubnet);
subnetSlug =
if builtins.hasAttr typeName subnetSlugs
then subnetSlugs.${typeName}
else typeName;
in
listToAttrs (imap1
(idx: hostName:
let
path = "${locationName}.${networkName}.${typeName}.${hostName}";
hostCfg = hostsForSubnet.${hostName};
hostScope = mergeScopes scope hostCfg;
roleName = hostRole path hostCfg;
# Count how many hosts of this role exist up to and including this one.
roleIndex =
let
prefix = lib.lists.sublist 0 idx hostNames;
in
length (filter
(hn: (hostsForSubnet.${hn}.role or null) == roleName)
prefix);
hostId = hostCfg.hostId or roleIndex;
ip = mkIpNamed {
location = loc;
inherit typeName;
roleName = roleName;
host = hostId;
};
roleSlug =
if builtins.hasAttr roleName roleSlugs
then roleSlugs.${roleName}
else roleName;
hostnameBase = "${roleSlug}-${builtins.toString hostId}";
hostname = hostCfg.hostname or hostnameBase;
in
nameValuePair hostName (
{
ip-address = ip;
inherit hostname;
location = locationName;
network = networkName;
subnetType = typeName;
subnetKey = "${locationName}-${networkName}-${typeName}";
}
// optionalAttrs (hostScope.domain != null) {
fqdn = "${hostname}.${subnetSlug}.${locCode}.${hostScope.domain}";
}
// materializeScope hostScope
// optionalAttrs (hostCfg ? hw-address) {
inherit (hostCfg) hw-address;
}
// optionalAttrs (hostCfg ? dns) {
inherit (hostCfg) dns;
}
// optionalAttrs (hostCfg ? aliases) {
inherit (hostCfg) aliases;
}
// optionalAttrs (hostCfg ? interface) {
inherit (hostCfg) interface;
}
// optionalAttrs (hostCfg ? tags) {
inherit (hostCfg) tags;
}
))
hostNames);
mkHostsFromSpec =
spec:
let
locations = spec.locations or (throw "metatron-addressing: spec.locations is required");
locationId = locationIdForSpec spec;
in
concatMapAttrs
(locationName: locationCfg:
let
loc = locationId locationName;
locCode = mkLocationCode locationName;
locationScope = mergeScopes emptyScope locationCfg;
networks = locationCfg.networks or { };
in
concatMapAttrs
(networkName: networkCfg:
let
networkScope = mergeScopes locationScope networkCfg;
subnets = networkCfg.subnets or { };
in
concatMapAttrs
(typeName: subnetCfg:
let
subnetScope = mergeScopes networkScope subnetCfg;
in
hostMapForSubnet {
inherit loc locCode locationName networkName typeName subnetCfg;
scope = subnetScope;
})
subnets)
networks)
locations;
mkSubnetsFromSpec =
spec:
let
locations = spec.locations or (throw "metatron-addressing: spec.locations is required");
locationId = locationIdForSpec spec;
in
concatMapAttrs
(locationName: locationCfg:
let
loc = locationId locationName;
locCode = mkLocationCode locationName;
locationScope = mergeScopes emptyScope locationCfg;
networks = locationCfg.networks or { };
in
concatMapAttrs
(networkName: networkCfg:
let
networkScope = mergeScopes locationScope networkCfg;
subnets = networkCfg.subnets or { };
in
mapAttrs'
(typeName: subnetCfg:
let
subnetScope = mergeScopes networkScope subnetCfg;
cidr = mkSubnetNamed { location = loc; inherit typeName; };
subnetSlug =
if builtins.hasAttr typeName subnetSlugs
then subnetSlugs.${typeName}
else typeName;
vlan =
subnetCfg.vlan or
subnetCfg._vlan or
(mkVlanNamed { location = loc; inherit typeName; });
dhcpCfg = subnetCfg.dhcp or null;
dhcpRange =
if dhcpCfg == null then null else {
startIp = mkIpNamed {
location = loc;
inherit typeName;
roleName = "pool";
host = dhcpCfg.start;
};
endIp = mkIpNamed {
location = loc;
inherit typeName;
roleName = "pool";
host = dhcpCfg.end;
};
};
in
nameValuePair
"${locationName}-${networkName}-${typeName}"
(
{
inherit cidr locationName networkName typeName vlan;
}
// optionalAttrs (subnetScope.domain != null) {
zone = "${subnetSlug}.${locCode}.${subnetScope.domain}";
}
// materializeScope subnetScope
// optionalAttrs (dhcpCfg != null) {
dhcp = dhcpCfg;
dhcpRange = dhcpRange;
}
))
subnets)
networks)
locations;
mkNetworkFromSpec = spec: {
hosts = mkHostsFromSpec spec;
subnets = mkSubnetsFromSpec spec;
};
in
{
inherit
subnetTypes
roles
thirdOctet
decodeThirdOctet
mkIp
mkIpNamed
mkSubnet
mkSubnetNamed
mkVlan
mkVlanNamed
mkHostsFromSpec
mkSubnetsFromSpec
mkNetworkFromSpec
mkRoleRange
mkRoleRangeNamed
mergeScopes;
# Shorthand; you were calling mkHosts before.
mkHosts = mkNetworkFromSpec;
}