inventory: refactor role resolution into submodule

This commit is contained in:
Johannes Kirschbauer
2025-02-05 18:00:15 +07:00
parent 85c432b4b1
commit 9faf221b3e
3 changed files with 120 additions and 91 deletions

View File

@@ -60,6 +60,7 @@ let
legacyResolveImports = legacyResolveImports =
{ {
supportedRoles, supportedRoles,
resolvedRolesPerInstance,
serviceConfigs, serviceConfigs,
serviceName, serviceName,
machineName, machineName,
@@ -69,18 +70,7 @@ let
# : [ Modules ] -> String -> ServiceConfig -> [ Modules ] # : [ Modules ] -> String -> ServiceConfig -> [ Modules ]
acc2: instanceName: serviceConfig: acc2: instanceName: serviceConfig:
let let
resolvedRoles = lib.genAttrs supportedRoles ( resolvedRoles = resolvedRolesPerInstance.${instanceName};
roleName:
resolveTags {
members = serviceConfig.roles.${roleName} or { };
inherit
serviceName
instanceName
roleName
inventory
;
}
);
isInService = builtins.any (members: builtins.elem machineName members.machines) ( isInService = builtins.any (members: builtins.elem machineName members.machines) (
builtins.attrValues resolvedRoles builtins.attrValues resolvedRoles
@@ -90,6 +80,7 @@ let
machineRoles = builtins.attrNames ( machineRoles = builtins.attrNames (
lib.filterAttrs (_role: roleConfig: builtins.elem machineName roleConfig.machines) resolvedRoles lib.filterAttrs (_role: roleConfig: builtins.elem machineName roleConfig.machines) resolvedRoles
); );
machineServiceConfig = (serviceConfig.machines.${machineName} or { }).config or { }; machineServiceConfig = (serviceConfig.machines.${machineName} or { }).config or { };
globalConfig = serviceConfig.config or { }; globalConfig = serviceConfig.config or { };
@@ -99,7 +90,7 @@ let
acc: role: acc ++ serviceConfig.roles.${role}.extraModules or [ ] acc: role: acc ++ serviceConfig.roles.${role}.extraModules or [ ]
) [ ] machineRoles; ) [ ] machineRoles;
# TODO: maybe optimize this dont lookup the role in inverse roles. Imports are not lazy # TODO: maybe optimize this don't lookup the role in inverse roles. Imports are not lazy
roleModules = builtins.map ( roleModules = builtins.map (
role: role:
if builtins.elem role supportedRoles && inventory.modules ? ${serviceName} then if builtins.elem role supportedRoles && inventory.modules ? ${serviceName} then
@@ -117,28 +108,14 @@ let
extraModules = map (s: if builtins.typeOf s == "string" then "${directory}/${s}" else s) ( extraModules = map (s: if builtins.typeOf s == "string" then "${directory}/${s}" else s) (
globalExtraModules ++ machineExtraModules ++ roleServiceExtraModules globalExtraModules ++ machineExtraModules ++ roleServiceExtraModules
); );
nonExistingRoles = builtins.filter (role: !(builtins.elem role supportedRoles)) (
builtins.attrNames (serviceConfig.roles or { })
);
constraintAssertions = clan-core.lib.modules.checkConstraints {
moduleName = serviceName;
allModules = inventory.modules;
inherit resolvedRoles instanceName;
};
in in
if (nonExistingRoles != [ ]) then if !(serviceConfig.enabled or true) then
throw "Roles ${builtins.toString nonExistingRoles} are not defined in the service ${serviceName}."
else if !(serviceConfig.enabled or true) then
acc2 acc2
else if isInService then else if isInService then
acc2 acc2
++ [ ++ [
{ {
imports = roleModules ++ extraModules; imports = roleModules ++ extraModules;
clan.inventory.assertions = constraintAssertions;
clan.inventory.services.${serviceName}.${instanceName} = { clan.inventory.services.${serviceName}.${instanceName} = {
roles = resolvedRoles; roles = resolvedRoles;
# TODO: Add inverseRoles to the service config if needed # TODO: Add inverseRoles to the service config if needed
@@ -162,7 +139,9 @@ let
) [ ] (serviceConfigs)); ) [ ] (serviceConfigs));
in in
{ {
imports = [ ./interface.nix ]; imports = [
./interface.nix
];
config = { config = {
machines = builtins.mapAttrs ( machines = builtins.mapAttrs (
machineName: machineConfig: m: machineName: machineConfig: m:
@@ -173,6 +152,26 @@ in
{ config, ... }: { config, ... }:
let let
serviceName = config.serviceName; serviceName = config.serviceName;
getRoleFile = role: builtins.seq role inventory.modules.${serviceName} + "/roles/${role}.nix";
in
{
_module.args = {
inherit
resolveTags
inventory
clan-core
machineName
serviceConfigs
;
};
imports = [
./roles.nix
];
isClanModule =
let
firstRole = import (getRoleFile (builtins.head config.supportedRoles));
loadModuleForClassCheck = loadModuleForClassCheck =
m: m:
if lib.isFunction m then if lib.isFunction m then
@@ -182,59 +181,9 @@ in
m args m args
else else
m; m;
firstRole = import (getRoleFile (builtins.head config.supportedRoles)); module = loadModuleForClassCheck (firstRole);
getRoleFile = role: builtins.seq role inventory.modules.${serviceName} + "/roles/${role}.nix";
resolvedRolesPerInstance = lib.mapAttrs (
instanceName: instanceConfig:
let
resolvedRoles = lib.genAttrs config.supportedRoles (
roleName:
resolveTags {
members = instanceConfig.roles.${roleName} or { };
inherit
instanceName
serviceName
roleName
inventory
;
}
);
usedRoles = builtins.attrNames instanceConfig.roles;
unmatchedRoles = builtins.filter (role: !builtins.elem role config.supportedRoles) usedRoles;
in in
if unmatchedRoles != [ ] then if (module) ? _class then module._class == "clan" else false;
throw ''
Service: '${serviceName}' Instance: '${instanceName}'
The following roles do not exist: ${builtins.toJSON unmatchedRoles}
Please use one of available roles: ${builtins.toJSON config.supportedRoles}
''
else
resolvedRoles
) serviceConfigs;
in
{
# Roles resolution
# : List String
supportedRoles = clan-core.lib.modules.getRoles inventory.modules serviceName;
matchedRoles = builtins.attrNames (
lib.filterAttrs (_: ms: builtins.elem machineName ms) config.machinesRoles
);
inherit resolvedRolesPerInstance;
isClanModule =
let
module = loadModuleForClassCheck firstRole;
in
if module ? _class then module._class == "clan" else false;
machinesRoles = builtins.zipAttrsWith (
_n: vs:
let
flat = builtins.foldl' (acc: s: acc ++ s.machines) [ ] vs;
in
lib.unique flat
) (builtins.attrValues resolvedRolesPerInstance);
# The actual result # The actual result
machineImports = machineImports =
if config.isClanModule then if config.isClanModule then
@@ -242,6 +191,7 @@ in
else else
legacyResolveImports { legacyResolveImports {
supportedRoles = config.supportedRoles; supportedRoles = config.supportedRoles;
resolvedRolesPerInstance = config.resolvedRolesPerInstance;
inherit inherit
serviceConfigs serviceConfigs
serviceName serviceName
@@ -280,7 +230,7 @@ in
; ;
}; };
machineImports = machineImports = (
compiledMachine.machineImports compiledMachine.machineImports
++ builtins.foldl' ( ++ builtins.foldl' (
acc: service: acc: service:
@@ -294,14 +244,26 @@ in
} }
] ]
else else
[ ]; [
{
clan.inventory.assertions = {
"alive.assertion.inventory" = {
assertion = true;
message = ''
No failed assertions found for machine ${machineName}. This will never be displayed.
It is here for testing purposes.
'';
};
};
}
];
in in
acc acc
++ service.machineImports ++ service.machineImports
# Import failed assertions # Import failed assertions
++ failedAssertionsImports ++ failedAssertionsImports
) [ ] (builtins.attrValues m.config.compiledServices); ) [ ] (builtins.attrValues m.config.compiledServices)
);
in in
{ {
inherit machineImports compiledServices compiledMachine; inherit machineImports compiledServices compiledMachine;

View File

@@ -0,0 +1,65 @@
{
lib,
config,
resolveTags,
inventory,
clan-core,
machineName,
serviceConfigs,
...
}:
let
serviceName = config.serviceName;
in
{
# Roles resolution
# : List String
supportedRoles = clan-core.lib.modules.getRoles inventory.modules serviceName;
matchedRoles = builtins.attrNames (
lib.filterAttrs (_: ms: builtins.elem machineName ms) config.machinesRoles
);
resolvedRolesPerInstance = lib.mapAttrs (
instanceName: instanceConfig:
let
resolvedRoles = lib.genAttrs config.supportedRoles (
roleName:
resolveTags {
members = instanceConfig.roles.${roleName} or { };
inherit
instanceName
serviceName
roleName
inventory
;
}
);
usedRoles = builtins.attrNames instanceConfig.roles;
unmatchedRoles = builtins.filter (role: !builtins.elem role config.supportedRoles) usedRoles;
in
if unmatchedRoles != [ ] then
throw ''
Roles ${builtins.toJSON unmatchedRoles} are not defined in the service ${serviceName}.
Instance: '${instanceName}'
Please use one of available roles: ${builtins.toJSON config.supportedRoles}
''
else
resolvedRoles
) serviceConfigs;
machinesRoles = builtins.zipAttrsWith (
_n: vs:
let
flat = builtins.foldl' (acc: s: acc ++ s.machines) [ ] vs;
in
lib.unique flat
) (builtins.attrValues config.resolvedRolesPerInstance);
assertions = lib.concatMapAttrs (
instanceName: resolvedRoles:
clan-core.lib.modules.checkConstraints {
moduleName = serviceName;
allModules = inventory.modules;
inherit resolvedRoles instanceName;
}
) config.resolvedRolesPerInstance;
}

View File

@@ -231,7 +231,7 @@ in
expr = configs.machines.machine_1.machineImports; expr = configs.machines.machine_1.machineImports;
expectedError = { expectedError = {
type = "ThrownError"; type = "ThrownError";
msg = "Roles roleXYZ are not defined in the service borgbackup."; msg = ''Roles \["roleXYZ"\] are not defined in the service borgbackup'';
}; };
}; };
# Needs NIX_ABORT_ON_WARN=1 # Needs NIX_ABORT_ON_WARN=1
@@ -286,7 +286,9 @@ in
in in
{ {
inherit configs; inherit configs;
expr = builtins.filter (v: v != { }) configs.machines.machine_1.machineImports; expr = builtins.filter (
v: v != { } && !v.clan.inventory.assertions ? "alive.assertion.inventory"
) configs.machines.machine_1.machineImports;
expected = [ ]; expected = [ ];
}; };
} }