Inventory/constraints: add id's to inventory constraints to make them more observable

This commit is contained in:
Johannes Kirschbauer
2024-11-13 13:58:43 +01:00
parent b4e34e7f40
commit 0a59803616
6 changed files with 255 additions and 229 deletions

View File

@@ -41,8 +41,155 @@ let
serviceName:
builtins.elem "inventory" (clan-core.lib.modules.getFrontmatter serviceName).features or [ ];
trimExtension = name: builtins.substring 0 (builtins.stringLength name - 4) name;
extendMachine =
{ machineConfig, inventory }:
[
(lib.optionalAttrs (machineConfig.deploy.targetHost or null != null) {
config.clan.core.networking.targetHost = machineConfig.deploy.targetHost;
})
{
assertions = lib.foldlAttrs (
acc: serviceName: _serviceConfigs:
acc
++ [
{
assertion = checkService serviceName;
message = ''
Service ${serviceName} cannot be used in inventory. It does not declare the 'inventory' feature.
To allow it add the following to the beginning of the README.md of the module:
---
...
features = [ "inventory" ]
---
Also make sure to test the module with the 'inventory' feature enabled.
'';
}
]
) [ ] inventory.services;
}
];
mapMachineConfigToNixOSConfig =
# Returns a NixOS configuration for the machine 'machineName'.
# Return Format: { imports = [ ... ]; config = { ... }; options = { ... } }
{
machineName,
machineConfig,
inventory,
directory,
}:
lib.foldlAttrs (
# [ Modules ], String, { ${instance_name} :: ServiceConfig }
initialServiceModules: serviceName: serviceConfigs:
initialServiceModules
# Collect service config
++ (lib.foldlAttrs (
# [ Modules ], String, ServiceConfig
acc2: instanceName: serviceConfig:
let
roles = clan-core.lib.modules.getRoles' serviceName;
resolvedRoles = lib.genAttrs roles (
roleName:
resolveTags {
members = serviceConfig.roles.${roleName} or { };
inherit
serviceName
instanceName
roleName
inventory
;
}
);
isInService = builtins.any (members: builtins.elem machineName members.machines) (
builtins.attrValues resolvedRoles
);
# all roles where the machine is present
machineRoles = builtins.attrNames (
lib.filterAttrs (_role: roleConfig: builtins.elem machineName roleConfig.machines) resolvedRoles
);
machineServiceConfig = (serviceConfig.machines.${machineName} or { }).config or { };
globalConfig = serviceConfig.config or { };
globalExtraModules = serviceConfig.extraModules or [ ];
machineExtraModules = serviceConfig.machines.${machineName}.extraModules or [ ];
roleServiceExtraModules = builtins.foldl' (
acc: role: acc ++ serviceConfig.roles.${role}.extraModules or [ ]
) [ ] machineRoles;
# TODO: maybe optimize this dont lookup the role in inverse roles. Imports are not lazy
roleModules = builtins.map (
role:
if builtins.elem role roles && clan-core.clanModules ? ${serviceName} then
clan-core.clanModules.${serviceName} + "/roles/${role}.nix"
else
throw "Module ${serviceName} doesn't have role: '${role}'. Role: ${
clan-core.clanModules.${serviceName}
}/roles/${role}.nix not found."
) machineRoles;
roleServiceConfigs = builtins.filter (m: m != { }) (
builtins.map (role: serviceConfig.roles.${role}.config or { }) machineRoles
);
extraModules = map (s: if builtins.typeOf s == "string" then "${directory}/${s}" else s) (
globalExtraModules ++ machineExtraModules ++ roleServiceExtraModules
);
nonExistingRoles = builtins.filter (role: !(builtins.elem role roles)) (
builtins.attrNames (serviceConfig.roles or { })
);
constraintAssertions = clan-core.lib.modules.checkConstraints {
moduleName = serviceName;
inherit resolvedRoles instanceName;
instanceNames = builtins.attrNames serviceConfigs;
};
in
if (nonExistingRoles != [ ]) then
throw "Roles ${builtins.toString nonExistingRoles} are not defined in the service ${serviceName}."
else if !(serviceConfig.enabled or true) then
acc2
else if isInService then
acc2
++ [
{
imports = roleModules ++ extraModules;
clan.inventory.assertions = constraintAssertions;
clan.inventory.services.${serviceName}.${instanceName} = {
roles = resolvedRoles;
# TODO: Add inverseRoles to the service config if needed
# inherit inverseRoles;
};
}
(lib.optionalAttrs (globalConfig != { } || machineServiceConfig != { } || roleServiceConfigs != [ ])
{
clan.${serviceName} = lib.mkMerge (
[
globalConfig
machineServiceConfig
]
++ roleServiceConfigs
);
}
)
]
else
acc2
) [ ] (serviceConfigs))
) [ ] inventory.services
# Global extension for each machine
++ (extendMachine { inherit machineConfig inventory; });
/*
Returns a NixOS configuration for every machine in the inventory.
@@ -54,150 +201,14 @@ let
# For each machine generate config, forEach service, if the machine is used.
builtins.mapAttrs (
machineName: machineConfig:
lib.foldlAttrs (
# [ Modules ], String, { ${instance_name} :: ServiceConfig }
acc: serviceName: serviceConfigs:
acc
# Collect service config
++ (lib.foldlAttrs (
# [ Modules ], String, ServiceConfig
acc2: instanceName: serviceConfig:
let
roles = lib.mapAttrsToList (name: _value: trimExtension name) (
lib.filterAttrs (name: type: type == "regular" && lib.hasSuffix ".nix" name) (
builtins.readDir (
if clan-core.clanModules ? ${serviceName} then
clan-core.clanModules.${serviceName} + "/roles"
else
throw "ClanModule not found: '${serviceName}'. Make sure the module is added in the 'clanModules' attribute of clan-core."
)
)
);
resolvedRoles = lib.genAttrs roles (
roleName:
resolveTags {
members = serviceConfig.roles.${roleName} or { };
inherit
serviceName
instanceName
roleName
inventory
;
}
);
isInService = builtins.any (members: builtins.elem machineName members.machines) (
builtins.attrValues resolvedRoles
);
# all roles where the machine is present
machineRoles = builtins.attrNames (
lib.filterAttrs (_role: roleConfig: builtins.elem machineName roleConfig.machines) resolvedRoles
);
machineServiceConfig = (serviceConfig.machines.${machineName} or { }).config or { };
globalConfig = serviceConfig.config or { };
globalExtraModules = serviceConfig.extraModules or [ ];
machineExtraModules = serviceConfig.machines.${machineName}.extraModules or [ ];
roleServiceExtraModules = builtins.foldl' (
acc: role: acc ++ serviceConfig.roles.${role}.extraModules or [ ]
) [ ] machineRoles;
# TODO: maybe optimize this dont lookup the role in inverse roles. Imports are not lazy
roleModules = builtins.map (
role:
if builtins.elem role roles && clan-core.clanModules ? ${serviceName} then
clan-core.clanModules.${serviceName} + "/roles/${role}.nix"
else
throw "Module ${serviceName} doesn't have role: '${role}'. Role: ${
clan-core.clanModules.${serviceName}
}/roles/${role}.nix not found."
) machineRoles;
roleServiceConfigs = builtins.filter (m: m != { }) (
builtins.map (role: serviceConfig.roles.${role}.config or { }) machineRoles
);
extraModules = map (s: if builtins.typeOf s == "string" then "${directory}/${s}" else s) (
globalExtraModules ++ machineExtraModules ++ roleServiceExtraModules
);
nonExistingRoles = builtins.filter (role: !(builtins.elem role roles)) (
builtins.attrNames (serviceConfig.roles or { })
);
constraintAssertions = clan-core.lib.modules.checkConstraints {
moduleName = serviceName;
inherit resolvedRoles;
};
in
if (nonExistingRoles != [ ]) then
throw "Roles ${builtins.toString nonExistingRoles} are not defined in the service ${serviceName}."
else if !(serviceConfig.enabled or true) then
acc2
else if isInService then
acc2
++ [
{
imports = roleModules ++ extraModules;
}
(lib.optionalAttrs (globalConfig != { } || machineServiceConfig != { } || roleServiceConfigs != [ ])
{
config.clan.${serviceName} = lib.mkMerge (
[
globalConfig
machineServiceConfig
]
++ roleServiceConfigs
);
}
)
({
assertions = constraintAssertions;
clan.inventory.services.${serviceName}.${instanceName} = {
roles = resolvedRoles;
# TODO: Add inverseRoles to the service config if needed
# inherit inverseRoles;
};
})
]
else
acc2
) [ ] (serviceConfigs))
) [ ] inventory.services
# Append each machine config
++ [
(lib.optionalAttrs (machineConfig.deploy.targetHost or null != null) {
config.clan.core.networking.targetHost = machineConfig.deploy.targetHost;
})
{
assertions = lib.foldlAttrs (
acc: serviceName: _:
acc
++ [
{
assertion = checkService serviceName;
message = ''
Service ${serviceName} cannot be used in inventory. It does not declare the 'inventory' feature.
To allow it add the following to the beginning of the README.md of the module:
---
...
features = [ "inventory" ]
---
Also make sure to test the module with the 'inventory' feature enabled.
'';
}
]
) [ ] inventory.services;
}
]
mapMachineConfigToNixOSConfig {
inherit
machineName
machineConfig
inventory
directory
;
}
) (inventory.machines or { });
in
{