From 316e33f54ae2c8aa3cd39821917772ad1b4e7079 Mon Sep 17 00:00:00 2001 From: Johannes Kirschbauer Date: Mon, 3 Feb 2025 08:34:21 +0100 Subject: [PATCH] Inventory: refactor build-inventory in more independent parts --- lib/build-clan/module.nix | 2 +- lib/inventory/build-inventory/default.nix | 302 +++++++++++----------- 2 files changed, 159 insertions(+), 145 deletions(-) diff --git a/lib/build-clan/module.nix b/lib/build-clan/module.nix index ed7ed475b..c431be07c 100644 --- a/lib/build-clan/module.nix +++ b/lib/build-clan/module.nix @@ -76,7 +76,7 @@ let (machines.${name} or { }) # Inherit the inventory assertions ? # { inherit (mergedInventory) assertions; } - { imports = serviceConfigs.${name} or [ ]; } + { imports = serviceConfigs.machines.${name}.machineImports or []; } ( { # Settings diff --git a/lib/inventory/build-inventory/default.nix b/lib/inventory/build-inventory/default.nix index 63f5e7678..1cb54001a 100644 --- a/lib/inventory/build-inventory/default.nix +++ b/lib/inventory/build-inventory/default.nix @@ -42,175 +42,189 @@ let builtins.elem "inventory" (clan-core.lib.modules.getFrontmatter modulepath serviceName).features or [ ]; - 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 inventory.modules.${serviceName} serviceName; - message = '' - Service ${serviceName} cannot be used in inventory. It does not declare the 'inventory' feature. + compileMachine = + { machineConfig }: + { + machineImports = [ + (lib.optionalAttrs (machineConfig.deploy.targetHost or null != null) { + config.clan.core.networking.targetHost = machineConfig.deploy.targetHost; + }) + ]; + assertions = { }; + }; - - 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 = + compileServicesForMachine = # 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 + compileServiceModules = serviceName: serviceConfigs: { + # TODO: Add other attributes + machineImports = ( + lib.foldlAttrs ( + # [ Modules ], String, ServiceConfig + acc2: instanceName: serviceConfig: - let - roles = clan-core.lib.modules.getRoles inventory.modules serviceName; + let + roles = clan-core.lib.modules.getRoles inventory.modules serviceName; - resolvedRoles = lib.genAttrs roles ( - roleName: - resolveTags { - members = serviceConfig.roles.${roleName} or { }; - inherit - serviceName - instanceName - roleName - inventory - ; - } - ); + 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 - ); + 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 { }; + # 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; + 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 && inventory.modules ? ${serviceName} then - inventory.modules.${serviceName} + "/roles/${role}.nix" - else - throw "Module ${serviceName} doesn't have role: '${role}'. Role: ${ - inventory.modules.${serviceName} - }/roles/${role}.nix not found." - ) 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 && inventory.modules ? ${serviceName} then + inventory.modules.${serviceName} + "/roles/${role}.nix" + else + throw "Module ${serviceName} doesn't have role: '${role}'. Role: ${ + inventory.modules.${serviceName} + }/roles/${role}.nix not found." + ) machineRoles; - roleServiceConfigs = builtins.filter (m: m != { }) ( - builtins.map (role: serviceConfig.roles.${role}.config or { }) 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 - ); + 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 { }) - ); + nonExistingRoles = builtins.filter (role: !(builtins.elem role roles)) ( + builtins.attrNames (serviceConfig.roles or { }) + ); - constraintAssertions = clan-core.lib.modules.checkConstraints { - moduleName = serviceName; - allModules = inventory.modules; - inherit resolvedRoles instanceName; - }; - 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; + constraintAssertions = clan-core.lib.modules.checkConstraints { + moduleName = serviceName; + allModules = inventory.modules; + inherit resolvedRoles instanceName; }; - } - (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; }); + 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) + ); + + assertions = lib.mapAttrs' (name: value: { + name = "checkservice.${serviceName}.${name}"; + value = { + assertion = checkService inventory.modules.${serviceName} 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; + }; + + in + lib.mapAttrs compileServiceModules inventory.services; + /* - Returns a NixOS configuration for every machine in the inventory. + Returns a set with NixOS configuration for every machine in the inventory. machinesFromInventory :: Inventory -> { ${machine_name} :: NixOSConfiguration } */ buildInventory = { inventory, directory }: - # For every machine in the inventory, build a NixOS configuration - # For each machine generate config, forEach service, if the machine is used. - builtins.mapAttrs ( - machineName: machineConfig: - mapMachineConfigToNixOSConfig { - inherit - machineName - machineConfig - inventory - directory - ; - } - ) (inventory.machines or { }); + { + machines = builtins.mapAttrs ( + machineName: machineConfig: + let + compiledServices = compileServicesForMachine { + inherit + machineName + inventory + directory + ; + }; + compiledMachine = compileMachine { + inherit + machineConfig + ; + }; + machineImports = + compiledMachine.machineImports + ++ builtins.foldl' (acc: service: acc ++ service.machineImports) [ ] ( + builtins.attrValues compiledServices + ); + in + { + inherit machineImports compiledServices compiledMachine; + } + ) (inventory.machines or { }); + }; in { inherit buildInventory;