feat(inventory/instances): prevent modules without explizit class from beeing used

This commit is contained in:
Johannes Kirschbauer
2025-04-02 14:57:21 +02:00
parent 542a6a3fd1
commit 11f213f8e2
4 changed files with 135 additions and 9 deletions

View File

@@ -63,6 +63,7 @@ let
resolvedModule =
resolvedModuleSet.${instance.module.name}
or (throw "flake doesn't provide clan-module with name ${instance.module.name}");
moduleClass = clanLib.inventory.getModuleClass resolvedModule;
# Every instance includes machines via roles
# :: { client :: ... }
@@ -86,13 +87,13 @@ let
machineName:
let
machineSettings = instance.roles.${roleName}.machines.${machineName}.settings or { };
# TODO: tag settings
# Wait for this feature until option introspection for 'settings' is done.
# This might get too complex to handle otherwise.
# settingsViaTags = lib.filterAttrs (
# tagName: _: machineHasTag machineName tagName
# ) instance.roles.${roleName}.tags;
in
# TODO: tag settings
# Wait for this feature until option introspection for 'settings' is done.
# This might get too complex to handle otherwise.
# settingsViaTags = lib.filterAttrs (
# tagName: _: machineHasTag machineName tagName
# ) instance.roles.${roleName}.tags;
{
# TODO: Do we want to wrap settings with
# setDefaultModuleLocation "inventory.instances.${instanceName}.roles.${roleName}.tags.${tagName}";
@@ -112,20 +113,29 @@ let
in
{
inherit (instance) module;
inherit resolvedModule instanceRoles;
inherit resolvedModule instanceRoles moduleClass;
}
) inventory.instances;
# TODO: Eagerly check the _class of the resolved module
importedModulesEvaluated = lib.mapAttrs (
_module_ident: instances:
let
matchedClass = "clan.service";
instance = (builtins.head instances).instance;
classCheckedModule =
if instance.moduleClass == matchedClass then
instance.resolvedModule
else
(throw ''Module '${instance.module.name}' is not a valid '${matchedClass}' module. Got module with class:${builtins.toJSON instance.moduleClass}'');
in
(lib.evalModules {
class = "clan.service";
class = matchedClass;
modules =
[
./service-module.nix
# Import the resolved module
(builtins.head instances).instance.resolvedModule
classCheckedModule
]
# Include all the instances that correlate to the resolved module
++ (builtins.map (v: {

View File

@@ -22,6 +22,15 @@ let
}).config;
flakeInputsFixture = {
# Example upstream module
upstream.clan.modules = {
uzzi = {
_class = "clan.service";
manifest = {
name = "uzzi-from-upstream";
};
};
};
};
callInventoryAdapter =
@@ -32,6 +41,7 @@ let
};
in
{
resolve_module_spec = import ./import_module_spec.nix { inherit lib callInventoryAdapter; };
test_simple =
let
res = callInventoryAdapter {

View File

@@ -0,0 +1,65 @@
{ callInventoryAdapter }:
let
# Authored module
# A minimal module looks like this
# It isn't exactly doing anything but it's a valid module that produces an output
modules."A" = {
_class = "clan.service";
manifest = {
name = "network";
};
};
modules."B" =
{ ... }:
{
options.stuff = "legacy-clan-service";
};
machines = {
jon = { };
sara = { };
};
resolve =
spec:
callInventoryAdapter {
inherit modules machines;
instances."instance_foo" = {
module = spec;
};
};
in
{
test_import_local_module_by_name = {
expr = (resolve { name = "A"; }).importedModuleWithInstances.instance_foo.resolvedModule;
expected = {
_class = "clan.service";
manifest = {
name = "network";
};
};
};
test_import_remote_module_by_name = {
expr =
(resolve {
name = "uzzi";
input = "upstream";
}).importedModuleWithInstances.instance_foo.resolvedModule;
expected = {
_class = "clan.service";
manifest = {
name = "uzzi-from-upstream";
};
};
};
# Currently this should fail
# TODO: Can we implement a default wrapper to make migration easy?
test_import_local_legacy_module = {
expr = (resolve { name = "B"; }).allMachines;
expectedError = {
type = "ThrownError";
msg = "Module 'B' is not a valid 'clan.service' module.*";
};
};
}