Better addressing?
This commit is contained in:
parent
b5c1a43863
commit
f35e402b4d
3 changed files with 394 additions and 261 deletions
|
|
@ -1,4 +1,4 @@
|
|||
{ lib ? import <nixpkgs/lib> { } }:
|
||||
{ lib, ... }:
|
||||
|
||||
let
|
||||
inherit (lib)
|
||||
|
|
@ -6,27 +6,26 @@ let
|
|||
attrNames
|
||||
concatMapAttrs
|
||||
concatStringsSep
|
||||
filterAttrs
|
||||
listToAttrs
|
||||
mapAttrs'
|
||||
nameValuePair
|
||||
optionalAttrs
|
||||
imap1
|
||||
mapAttrs'
|
||||
imap0
|
||||
length
|
||||
filter
|
||||
findFirst
|
||||
toLower;#
|
||||
|
||||
toLower
|
||||
unique;
|
||||
|
||||
mkInitials =
|
||||
name:
|
||||
let
|
||||
lower = toLower name; # was builtins.toLower
|
||||
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);
|
||||
letters = concatStringsSep "" (builtins.map (w: builtins.substring 0 1 w) words);
|
||||
in
|
||||
if letters == "" then "loc" else builtins.substring 0 3 letters;
|
||||
|
||||
|
|
@ -38,7 +37,7 @@ let
|
|||
in
|
||||
"${base}-${hash}";
|
||||
|
||||
# Manual elemIndex so we don't care what nixpkgs version you're on
|
||||
# Manual elemIndex so we do not care what nixpkgs version you are on.
|
||||
elemIndex =
|
||||
name: list:
|
||||
let
|
||||
|
|
@ -180,9 +179,9 @@ let
|
|||
mkRoleRangeNamed =
|
||||
{ location, typeName, roleName }:
|
||||
let
|
||||
_ = assertMsg (subnetTypes ? typeName)
|
||||
_ = assertMsg (builtins.hasAttr typeName subnetTypes)
|
||||
"metatron-addressing: unknown subnet type '${typeName}'";
|
||||
__ = assertMsg (roles ? roleName)
|
||||
__ = assertMsg (builtins.hasAttr roleName roles)
|
||||
"metatron-addressing: unknown role '${roleName}'";
|
||||
|
||||
type = subnetTypes.${typeName};
|
||||
|
|
@ -193,9 +192,9 @@ let
|
|||
mkIpNamed =
|
||||
{ location, typeName, roleName, host }:
|
||||
let
|
||||
_ = assertMsg (subnetTypes ? typeName)
|
||||
_ = assertMsg (builtins.hasAttr typeName subnetTypes)
|
||||
"metatron-addressing: unknown subnet type '${typeName}'";
|
||||
__ = assertMsg (roles ? roleName)
|
||||
__ = assertMsg (builtins.hasAttr roleName roles)
|
||||
"metatron-addressing: unknown role '${roleName}'";
|
||||
|
||||
type = subnetTypes.${typeName};
|
||||
|
|
@ -217,7 +216,7 @@ let
|
|||
mkSubnetNamed =
|
||||
{ location, typeName }:
|
||||
let
|
||||
_ = assertMsg (subnetTypes ? typeName)
|
||||
_ = assertMsg (builtins.hasAttr typeName subnetTypes)
|
||||
"metatron-addressing: unknown subnet type '${typeName}'";
|
||||
type = subnetTypes.${typeName};
|
||||
in
|
||||
|
|
@ -236,18 +235,59 @@ let
|
|||
mkVlanNamed =
|
||||
{ location, typeName }:
|
||||
let
|
||||
_ = assertMsg (subnetTypes ? typeName)
|
||||
_ = assertMsg (builtins.hasAttr typeName subnetTypes)
|
||||
"metatron-addressing: unknown subnet type '${typeName}'";
|
||||
type = subnetTypes.${typeName};
|
||||
in
|
||||
mkVlan { inherit location type; };
|
||||
|
||||
# locationName → numeric id (0, 1, 2, …)
|
||||
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");
|
||||
locations = spec.locations or (throw "metatron-addressing: spec.locations is required");
|
||||
locationNames = attrNames locations;
|
||||
in
|
||||
name:
|
||||
|
|
@ -259,183 +299,197 @@ let
|
|||
else
|
||||
idx;
|
||||
|
||||
# Just the host map: { deimos = { ip-address = "..."; fqdn = "..."; ... }; ... }
|
||||
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");
|
||||
locations = spec.locations or (throw "metatron-addressing: spec.locations is required");
|
||||
locationId = locationIdForSpec spec;
|
||||
domain =
|
||||
spec.domain or (throw "metatron-addressing: spec.domain is required for FQDN generation");
|
||||
in
|
||||
concatMapAttrs
|
||||
(locationName: locationCfg:
|
||||
let
|
||||
loc = locationId locationName;
|
||||
locCode = mkLocationCode locationName;
|
||||
|
||||
# Only treat attributes that are attrsets with a `hosts` attr as subnets
|
||||
subnets =
|
||||
lib.filterAttrs (_: v: builtins.isAttrs v && v ? hosts) locationCfg;
|
||||
locationScope = mergeScopes emptyScope locationCfg;
|
||||
networks = locationCfg.networks or { };
|
||||
in
|
||||
concatMapAttrs
|
||||
(typeName: subnetCfg:
|
||||
(networkName: networkCfg:
|
||||
let
|
||||
hostsForSubnet = subnetCfg.hosts or { };
|
||||
|
||||
# Only treat non-underscore-prefixed keys as hosts
|
||||
hostNames =
|
||||
filter (hn: builtins.substring 0 1 hn != "_") (attrNames hostsForSubnet);
|
||||
networkScope = mergeScopes locationScope networkCfg;
|
||||
subnets = networkCfg.subnets or { };
|
||||
in
|
||||
listToAttrs (imap1
|
||||
(idx: hostName:
|
||||
concatMapAttrs
|
||||
(typeName: subnetCfg:
|
||||
let
|
||||
hostCfg = hostsForSubnet.${hostName};
|
||||
roleName = hostCfg.role;
|
||||
subnetScope = mergeScopes networkScope subnetCfg;
|
||||
in
|
||||
hostMapForSubnet {
|
||||
inherit loc locCode locationName networkName typeName subnetCfg;
|
||||
scope = subnetScope;
|
||||
})
|
||||
subnets)
|
||||
networks)
|
||||
locations;
|
||||
|
||||
# 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);
|
||||
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;
|
||||
|
||||
hostId = hostCfg.hostId or roleIndex;
|
||||
|
||||
ip = mkIpNamed {
|
||||
location = loc;
|
||||
inherit typeName;
|
||||
roleName = hostCfg.role;
|
||||
host = hostId;
|
||||
};
|
||||
cidr = mkSubnetNamed { location = loc; inherit typeName; };
|
||||
|
||||
subnetSlug =
|
||||
if builtins.hasAttr typeName subnetSlugs
|
||||
then subnetSlugs.${typeName}
|
||||
else typeName;
|
||||
|
||||
roleSlug =
|
||||
if builtins.hasAttr roleName roleSlugs
|
||||
then roleSlugs.${roleName}
|
||||
else roleName;
|
||||
vlan =
|
||||
subnetCfg.vlan or
|
||||
subnetCfg._vlan or
|
||||
(mkVlanNamed { location = loc; inherit typeName; });
|
||||
|
||||
hostnameBase = "${roleSlug}-${builtins.toString hostId}";
|
||||
hostname = hostCfg.hostname or hostnameBase;
|
||||
dhcpCfg = subnetCfg.dhcp or null;
|
||||
|
||||
fqdn = "${hostname}.${subnetSlug}.${locCode}.${domain}";
|
||||
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 hostName (
|
||||
{
|
||||
ip-address = ip;
|
||||
inherit hostname fqdn;
|
||||
|
||||
# New context fields:
|
||||
location = locationName;
|
||||
subnetType = typeName;
|
||||
subnetKey = "${locationName}-${typeName}";
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
)
|
||||
)
|
||||
hostNames))
|
||||
subnets)
|
||||
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;
|
||||
|
||||
# Subnet map: { "home-dmz" = "10.1.0.0/19"; "home-main" = "..."; ... }
|
||||
mkSubnetsFromSpec =
|
||||
spec:
|
||||
let
|
||||
locations =
|
||||
spec.locations or (throw "metatron-addressing: spec.locations is required");
|
||||
locationId = locationIdForSpec spec;
|
||||
domain =
|
||||
spec.domain or (throw "metatron-addressing: spec.domain is required");
|
||||
in
|
||||
concatMapAttrs
|
||||
(locationName: locationCfg:
|
||||
let
|
||||
loc = locationId locationName;
|
||||
locCode = mkLocationCode locationName;
|
||||
|
||||
# Only attributes with `hosts` are subnets
|
||||
subnets =
|
||||
lib.filterAttrs (_: v: builtins.isAttrs v && v ? hosts) locationCfg;
|
||||
in
|
||||
mapAttrs'
|
||||
(typeName: subnetCfg:
|
||||
let
|
||||
cidr = mkSubnetNamed { location = loc; inherit typeName; };
|
||||
|
||||
subnetSlug =
|
||||
if builtins.hasAttr typeName subnetSlugs
|
||||
then subnetSlugs.${typeName}
|
||||
else typeName;
|
||||
|
||||
zone = "${subnetSlug}.${locCode}.${domain}";
|
||||
|
||||
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}-${typeName}"
|
||||
(
|
||||
{
|
||||
inherit cidr locationName typeName zone vlan;
|
||||
}
|
||||
// optionalAttrs (dhcpCfg != null) {
|
||||
dhcp = dhcpCfg;
|
||||
dhcpRange = dhcpRange;
|
||||
}
|
||||
))
|
||||
subnets)
|
||||
locations;
|
||||
|
||||
# Combined view, if you want both
|
||||
mkNetworkFromSpec =
|
||||
spec:
|
||||
let
|
||||
domain =
|
||||
spec.domain or (throw "metatron-addressing: spec.domain is required");
|
||||
in
|
||||
{
|
||||
inherit domain;
|
||||
hosts = mkHostsFromSpec spec;
|
||||
subnets = mkSubnetsFromSpec spec;
|
||||
};
|
||||
mkNetworkFromSpec = spec: {
|
||||
hosts = mkHostsFromSpec spec;
|
||||
subnets = mkSubnetsFromSpec spec;
|
||||
};
|
||||
|
||||
in
|
||||
{
|
||||
|
|
@ -448,12 +502,15 @@ in
|
|||
mkIpNamed
|
||||
mkSubnet
|
||||
mkSubnetNamed
|
||||
mkVlan
|
||||
mkVlanNamed
|
||||
mkHostsFromSpec
|
||||
mkSubnetsFromSpec
|
||||
mkNetworkFromSpec
|
||||
mkRoleRange
|
||||
mkRoleRangeNamed;
|
||||
mkRoleRangeNamed
|
||||
mergeScopes;
|
||||
|
||||
# Shorthand; you were calling mkHosts before
|
||||
# Shorthand; you were calling mkHosts before.
|
||||
mkHosts = mkNetworkFromSpec;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue