diff --git a/lib/inventory/default.nix b/lib/inventory/default.nix index 90f25ea44..3e75f13c3 100644 --- a/lib/inventory/default.nix +++ b/lib/inventory/default.nix @@ -2,11 +2,7 @@ lib, clanLib, }: -let - services = clanLib.callLib ./distributed-service/inventory-adapter.nix { }; -in { - inherit (services) mapInstances; inventoryModule = { _file = "clanLib.inventory.module"; imports = [ diff --git a/lib/inventory/distributed-service/all-services-wrapper.nix b/lib/inventory/distributed-service/all-services-wrapper.nix index 381f4c555..65cd40fb4 100644 --- a/lib/inventory/distributed-service/all-services-wrapper.nix +++ b/lib/inventory/distributed-service/all-services-wrapper.nix @@ -28,16 +28,15 @@ in elemType = submoduleWith { class = "clan.service"; specialArgs = { - exports = config.exports; directory = directory; clanLib = specialArgs.clanLib; + exports = config.exports; }; modules = [ ( { name, ... }: { _module.args._ctx = [ name ]; - } ) ./service-module.nix diff --git a/lib/inventory/distributed-service/inventory-adapter.nix b/lib/inventory/distributed-service/inventory-adapter.nix deleted file mode 100644 index 7c19997b3..000000000 --- a/lib/inventory/distributed-service/inventory-adapter.nix +++ /dev/null @@ -1,168 +0,0 @@ -# Adapter function between the inventory.instances and the clan.service module -# -# Data flow: -# - inventory.instances -> Adapter -> clan.service module -> Service Resources (i.e. NixosModules per Machine, Vars per Service, etc.) -# -# What this file does: -# -# - Resolves the [Module] to an actual module-path and imports it. -# - Groups together all the same modules into a single import and creates all instances for it. -# - Resolves the inventory tags into machines. Tags don't exist at the service level. -# Also combines the settings for 'machines' and 'tags'. -{ - lib, - clanLib, - ... -}: -{ - mapInstances = - { - # This is used to resolve the module imports from 'flake.inputs' - flakeInputs, - # The clan inventory - inventory, - directory, - clanCoreModules, - prefix ? [ ], - exportsModule, - }: - let - # machineHasTag = machineName: tagName: lib.elem tagName inventory.machines.${machineName}.tags; - - # map the instances into the module - importedModuleWithInstances = lib.mapAttrs ( - instanceName: instance: - let - resolvedModule = clanLib.resolveModule { - moduleSpec = instance.module; - inherit flakeInputs clanCoreModules; - }; - - # Every instance includes machines via roles - # :: { client :: ... } - instanceRoles = lib.mapAttrs ( - roleName: role: - let - resolvedMachines = clanLib.inventory.resolveTags { - members = { - # Explicit members - machines = lib.attrNames role.machines; - # Resolved Members - tags = lib.attrNames role.tags; - }; - inherit (inventory) machines; - inherit instanceName roleName; - }; - in - # instances..roles. = - # Remove "tags", they are resolved into "machines" - (removeAttrs role [ "tags" ]) - // { - machines = lib.genAttrs resolvedMachines.machines ( - machineName: - let - machineSettings = instance.roles.${roleName}.machines.${machineName}.settings or { }; - 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}"; - settings = { - imports = [ - machineSettings - ]; # ++ lib.attrValues (lib.mapAttrs (_tagName: v: v.settings) settingsViaTags); - }; - } - ); - } - ) instance.roles; - in - { - inherit (instance) module; - inherit resolvedModule instanceRoles; - } - ) inventory.instances or { }; - - # Group the instances by the module they resolve to - # This is necessary to evaluate the module in a single pass - # :: { _ :: [ { name, value } ] } - # Since 'perMachine' needs access to all the instances we should include them as a whole - grouped = lib.foldlAttrs ( - acc: instanceName: instance: - let - inputName = if instance.module.input == null then "" else instance.module.input; - id = inputName + "-" + instance.module.name; - in - acc - // { - ${id} = acc.${id} or [ ] ++ [ - { - inherit instanceName instance; - } - ]; - } - ) { } importedModuleWithInstances; - - # servicesEval.config.mappedServices.self-A.result.final.jon.nixosModule - allMachines = lib.mapAttrs (machineName: _: { - # This is the list of nixosModules for each machine - machineImports = lib.foldlAttrs ( - acc: _module_ident: serviceModule: - acc ++ [ serviceModule.result.final.${machineName}.nixosModule or { } ] - ) [ ] servicesEval.config.mappedServices; - }) inventory.machines or { }; - - evalServices = - { modules, prefix }: - lib.evalModules { - class = "clan"; - specialArgs = { - inherit clanLib; - _ctx = prefix; - }; - modules = [ - (import ./all-services-wrapper.nix { inherit directory; }) - ] - ++ modules; - }; - - servicesEval = evalServices { - inherit prefix; - modules = [ - { - inherit exportsModule; - mappedServices = lib.mapAttrs (_module_ident: instances: { - imports = [ - # Import the resolved module. - # i.e. clan.modules.admin - { - options.module = lib.mkOption { - type = lib.types.raw; - default = (builtins.head instances).instance.module; - }; - } - (builtins.head instances).instance.resolvedModule - ] # Include all the instances that correlate to the resolved module - ++ (builtins.map (v: { - instances.${v.instanceName}.roles = v.instance.instanceRoles; - }) instances); - }) grouped; - } - ]; - }; - in - { - inherit - servicesEval - importedModuleWithInstances - # Exposed for testing - grouped - allMachines - ; - }; -} diff --git a/lib/inventory/distributed-service/tests/default.nix b/lib/inventory/distributed-service/tests/default.nix index 048b0b14c..068e944a1 100644 --- a/lib/inventory/distributed-service/tests/default.nix +++ b/lib/inventory/distributed-service/tests/default.nix @@ -4,63 +4,53 @@ ... }: let - inherit (lib) - evalModules - ; - evalInventory = - m: - (evalModules { - # Static modules - modules = [ - clanLib.inventory.inventoryModule - { - _file = "test file"; - tags.all = [ ]; - tags.nixos = [ ]; - tags.darwin = [ ]; - } - { - modules.test = { }; - } - m - ]; - }).config; - - callInventoryAdapter = - inventoryModule: - let - inventory = evalInventory inventoryModule; - flakeInputsFixture = { - self.clan.modules = inventoryModule.modules or { }; - # Example upstream module - upstream.clan.modules = { - uzzi = { - _class = "clan.service"; - manifest = { - name = "uzzi-from-upstream"; - }; - }; + flakeInputsFixture = { + upstream.clan.modules = { + uzzi = { + _class = "clan.service"; + manifest = { + name = "uzzi-from-upstream"; }; }; - in - clanLib.inventory.mapInstances { - directory = ./.; - clanCoreModules = { }; - flakeInputs = flakeInputsFixture; - inherit inventory; - exportsModule = { }; }; + }; + + createTestClan = + testClan: + let + res = clanLib.clan ({ + # Static / mocked + specialArgs = { + clan-core = { + clan.modules = { }; + }; + }; + self.inputs = flakeInputsFixture // { + self.clan = res.config; + }; + directory = ./.; + exportsModule = { }; + + imports = [ + testClan + ]; + }); + in + res; + in { extraModules = import ./extraModules.nix { inherit clanLib; }; exports = import ./exports.nix { inherit lib clanLib; }; - settings = import ./settings.nix { inherit lib callInventoryAdapter; }; - specialArgs = import ./specialArgs.nix { inherit lib callInventoryAdapter; }; - resolve_module_spec = import ./import_module_spec.nix { inherit lib callInventoryAdapter; }; + settings = import ./settings.nix { inherit lib createTestClan; }; + specialArgs = import ./specialArgs.nix { inherit lib createTestClan; }; + resolve_module_spec = import ./import_module_spec.nix { + inherit lib createTestClan; + }; test_simple = let - res = callInventoryAdapter { + res = createTestClan { # Authored module # A minimal module looks like this # It isn't exactly doing anything but it's a valid module that produces an output @@ -71,7 +61,7 @@ in }; }; # User config - instances."instance_foo" = { + inventory.instances."instance_foo" = { module = { name = "simple-module"; }; @@ -81,7 +71,7 @@ in { # Test that the module is mapped into the output # We might change the attribute name in the future - expr = res.servicesEval.config.mappedServices ? "-simple-module"; + expr = res.config._services.mappedServices ? "-simple-module"; expected = true; inherit res; }; @@ -92,7 +82,7 @@ in # All instances should be included within one evaluation to make all of them available test_module_grouping = let - res = callInventoryAdapter { + res = createTestClan { # Authored module # A minimal module looks like this # It isn't exactly doing anything but it's a valid module that produces an output @@ -112,18 +102,19 @@ in perMachine = { }: { }; }; + # User config - instances."instance_foo" = { + inventory.instances."instance_foo" = { module = { name = "A"; }; }; - instances."instance_bar" = { + inventory.instances."instance_bar" = { module = { name = "B"; }; }; - instances."instance_baz" = { + inventory.instances."instance_baz" = { module = { name = "A"; }; @@ -133,16 +124,16 @@ in { # Test that the module is mapped into the output # We might change the attribute name in the future - expr = lib.mapAttrs (_n: v: builtins.length v) res.grouped; - expected = { - "-A" = 2; - "-B" = 1; - }; + expr = lib.attrNames res.config._services.mappedServices; + expected = [ + "-A" + "-B" + ]; }; test_creates_all_instances = let - res = callInventoryAdapter { + res = createTestClan { # Authored module # A minimal module looks like this # It isn't exactly doing anything but it's a valid module that produces an output @@ -154,22 +145,24 @@ in perMachine = { }: { }; }; - instances."instance_foo" = { - module = { - name = "A"; - input = "self"; + inventory = { + instances."instance_foo" = { + module = { + name = "A"; + input = "self"; + }; }; - }; - instances."instance_bar" = { - module = { - name = "A"; - input = "self"; + instances."instance_bar" = { + module = { + name = "A"; + input = "self"; + }; }; - }; - instances."instance_zaza" = { - module = { - name = "B"; - input = null; + instances."instance_zaza" = { + module = { + name = "B"; + input = null; + }; }; }; }; @@ -177,7 +170,7 @@ in { # Test that the module is mapped into the output # We might change the attribute name in the future - expr = lib.attrNames res.servicesEval.config.mappedServices.self-A.instances; + expr = lib.attrNames res.config._services.mappedServices.self-A.instances; expected = [ "instance_bar" "instance_foo" @@ -187,7 +180,7 @@ in # Membership via roles test_add_machines_directly = let - res = callInventoryAdapter { + res = createTestClan { # Authored module # A minimal module looks like this # It isn't exactly doing anything but it's a valid module that produces an output @@ -202,38 +195,40 @@ in # perMachine = {}: {}; }; - machines = { - jon = { }; - sara = { }; - hxi = { }; - }; - instances."instance_foo" = { - module = { - name = "A"; - input = "self"; + inventory = { + machines = { + jon = { }; + sara = { }; + hxi = { }; }; - roles.peer.machines.jon = { }; - }; - instances."instance_bar" = { - module = { - name = "A"; - input = "self"; + instances."instance_foo" = { + module = { + name = "A"; + input = "self"; + }; + roles.peer.machines.jon = { }; }; - roles.peer.machines.sara = { }; - }; - instances."instance_zaza" = { - module = { - name = "B"; - input = null; + instances."instance_bar" = { + module = { + name = "A"; + input = "self"; + }; + roles.peer.machines.sara = { }; + }; + instances."instance_zaza" = { + module = { + name = "B"; + input = null; + }; + roles.peer.tags.all = { }; }; - roles.peer.tags.all = { }; }; }; in { # Test that the module is mapped into the output # We might change the attribute name in the future - expr = lib.attrNames res.servicesEval.config.mappedServices.self-A.result.allMachines; + expr = lib.attrNames res.config._services.mappedServices.self-A.result.allMachines; expected = [ "jon" "sara" @@ -243,7 +238,7 @@ in # Membership via tags test_add_machines_via_tags = let - res = callInventoryAdapter { + res = createTestClan { # Authored module # A minimal module looks like this # It isn't exactly doing anything but it's a valid module that produces an output @@ -257,35 +252,37 @@ in # perMachine = {}: {}; }; - machines = { - jon = { - tags = [ "foo" ]; + inventory = { + machines = { + jon = { + tags = [ "foo" ]; + }; + sara = { + tags = [ "foo" ]; + }; + hxi = { }; }; - sara = { - tags = [ "foo" ]; + instances."instance_foo" = { + module = { + name = "A"; + input = "self"; + }; + roles.peer.tags.foo = { }; }; - hxi = { }; - }; - instances."instance_foo" = { - module = { - name = "A"; - input = "self"; + instances."instance_zaza" = { + module = { + name = "B"; + input = null; + }; + roles.peer.tags.all = { }; }; - roles.peer.tags.foo = { }; - }; - instances."instance_zaza" = { - module = { - name = "B"; - input = null; - }; - roles.peer.tags.all = { }; }; }; in { # Test that the module is mapped into the output # We might change the attribute name in the future - expr = lib.attrNames res.servicesEval.config.mappedServices.self-A.result.allMachines; + expr = lib.attrNames res.config._services.mappedServices.self-A.result.allMachines; expected = [ "jon" "sara" @@ -293,6 +290,9 @@ in }; machine_imports = import ./machine_imports.nix { inherit lib clanLib; }; - per_machine_args = import ./per_machine_args.nix { inherit lib callInventoryAdapter; }; - per_instance_args = import ./per_instance_args.nix { inherit lib callInventoryAdapter; }; + per_machine_args = import ./per_machine_args.nix { inherit lib createTestClan; }; + per_instance_args = import ./per_instance_args.nix { + inherit lib; + callInventoryAdapter = createTestClan; + }; } diff --git a/lib/inventory/distributed-service/tests/import_module_spec.nix b/lib/inventory/distributed-service/tests/import_module_spec.nix index a8d29538d..dde5a9934 100644 --- a/lib/inventory/distributed-service/tests/import_module_spec.nix +++ b/lib/inventory/distributed-service/tests/import_module_spec.nix @@ -1,4 +1,4 @@ -{ callInventoryAdapter, ... }: +{ createTestClan, ... }: let # Authored module # A minimal module looks like this @@ -23,10 +23,13 @@ let resolve = spec: - callInventoryAdapter { - inherit modules machines; - instances."instance_foo" = { - module = spec; + createTestClan { + inherit modules; + inventory = { + inherit machines; + instances."instance_foo" = { + module = spec; + }; }; }; in @@ -36,25 +39,16 @@ in (resolve { name = "A"; input = "self"; - }).importedModuleWithInstances.instance_foo.resolvedModule; - expected = { - _class = "clan.service"; - manifest = { - name = "network"; - }; - }; + }).config._services.mappedServices.self-A.manifest.name; + expected = "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"; - }; - }; + }).config._services.mappedServices.upstream-uzzi.manifest.name; + expected = "uzzi-from-upstream"; + }; } diff --git a/lib/inventory/distributed-service/tests/per_instance_args.nix b/lib/inventory/distributed-service/tests/per_instance_args.nix index 9fe0c0316..371ce9554 100644 --- a/lib/inventory/distributed-service/tests/per_instance_args.nix +++ b/lib/inventory/distributed-service/tests/per_instance_args.nix @@ -58,39 +58,43 @@ let sara = { }; }; res = callInventoryAdapter { - inherit modules machines; - instances."instance_foo" = { - module = { - name = "A"; - input = "self"; + inherit modules; + + inventory = { + inherit machines; + instances."instance_foo" = { + module = { + name = "A"; + input = "self"; + }; + roles.peer.machines.jon = { + settings.timeout = lib.mkForce "foo-peer-jon"; + }; + roles.peer = { + settings.timeout = "foo-peer"; + }; + roles.controller.machines.jon = { }; }; - roles.peer.machines.jon = { - settings.timeout = lib.mkForce "foo-peer-jon"; + instances."instance_bar" = { + module = { + name = "A"; + input = "self"; + }; + roles.peer.machines.jon = { + settings.timeout = "bar-peer-jon"; + }; }; - roles.peer = { - settings.timeout = "foo-peer"; + # TODO: move this into a seperate test. + # Seperate out the check that this module is never imported + # import the module "B" (undefined) + # All machines have this instance + instances."instance_zaza" = { + module = { + name = "B"; + input = null; + }; + roles.peer.tags.all = { }; }; - roles.controller.machines.jon = { }; - }; - instances."instance_bar" = { - module = { - name = "A"; - input = "self"; - }; - roles.peer.machines.jon = { - settings.timeout = "bar-peer-jon"; - }; - }; - # TODO: move this into a seperate test. - # Seperate out the check that this module is never imported - # import the module "B" (undefined) - # All machines have this instance - instances."instance_zaza" = { - module = { - name = "B"; - input = null; - }; - roles.peer.tags.all = { }; }; }; @@ -105,9 +109,10 @@ in { # settings should evaluate test_per_instance_arguments = { + inherit res; expr = { instanceName = - res.servicesEval.config.mappedServices.self-A.result.allRoles.peer.allInstances."instance_foo".allMachines.jon.passthru.instanceName; + res.config._services.mappedServices.self-A.result.allRoles.peer.allInstances."instance_foo".allMachines.jon.passthru.instanceName; # settings are specific. # Below we access: @@ -115,11 +120,11 @@ in # roles = peer # machines = jon settings = - res.servicesEval.config.mappedServices.self-A.result.allRoles.peer.allInstances.instance_foo.allMachines.jon.passthru.settings; + res.config._services.mappedServices.self-A.result.allRoles.peer.allInstances.instance_foo.allMachines.jon.passthru.settings; machine = - res.servicesEval.config.mappedServices.self-A.result.allRoles.peer.allInstances.instance_foo.allMachines.jon.passthru.machine; + res.config._services.mappedServices.self-A.result.allRoles.peer.allInstances.instance_foo.allMachines.jon.passthru.machine; roles = - res.servicesEval.config.mappedServices.self-A.result.allRoles.peer.allInstances.instance_foo.allMachines.jon.passthru.roles; + res.config._services.mappedServices.self-A.result.allRoles.peer.allInstances.instance_foo.allMachines.jon.passthru.roles; }; expected = { instanceName = "instance_foo"; @@ -160,9 +165,9 @@ in # TODO: Cannot be tested like this anymore test_per_instance_settings_vendoring = { - x = res.servicesEval.config.mappedServices.self-A; + x = res.config._services.mappedServices.self-A; expr = - res.servicesEval.config.mappedServices.self-A.result.allRoles.peer.allInstances.instance_foo.allMachines.jon.passthru.vendoredSettings; + res.config._services.mappedServices.self-A.result.allRoles.peer.allInstances.instance_foo.allMachines.jon.passthru.vendoredSettings; expected = { timeout = "config.thing"; }; diff --git a/lib/inventory/distributed-service/tests/per_machine_args.nix b/lib/inventory/distributed-service/tests/per_machine_args.nix index 5907e8bf2..b650281a6 100644 --- a/lib/inventory/distributed-service/tests/per_machine_args.nix +++ b/lib/inventory/distributed-service/tests/per_machine_args.nix @@ -1,4 +1,4 @@ -{ lib, callInventoryAdapter }: +{ lib, createTestClan }: let # Authored module # A minimal module looks like this @@ -39,36 +39,40 @@ let jon = { }; sara = { }; }; - res = callInventoryAdapter { - inherit modules machines; - instances."instance_foo" = { - module = { - name = "A"; - input = "self"; + res = createTestClan { + inherit modules; + inventory = { + + inherit machines; + instances."instance_foo" = { + module = { + name = "A"; + input = "self"; + }; + roles.peer.machines.jon = { + settings.timeout = lib.mkForce "foo-peer-jon"; + }; + roles.peer = { + settings.timeout = "foo-peer"; + }; }; - roles.peer.machines.jon = { - settings.timeout = lib.mkForce "foo-peer-jon"; + instances."instance_bar" = { + module = { + name = "A"; + input = "self"; + }; + roles.peer.machines.jon = { + settings.timeout = "bar-peer-jon"; + }; }; - roles.peer = { - settings.timeout = "foo-peer"; + instances."instance_zaza" = { + module = { + name = "B"; + input = null; + }; + roles.peer.tags.all = { }; }; }; - instances."instance_bar" = { - module = { - name = "A"; - input = "self"; - }; - roles.peer.machines.jon = { - settings.timeout = "bar-peer-jon"; - }; - }; - instances."instance_zaza" = { - module = { - name = "B"; - input = null; - }; - roles.peer.tags.all = { }; - }; }; in @@ -79,7 +83,7 @@ in inherit res; expr = { hasMachineSettings = - res.servicesEval.config.mappedServices.self-A.result.allMachines.jon.passthru.instances.instance_foo.roles.peer.machines.jon + res.config._services.mappedServices.self-A.result.allMachines.jon.passthru.instances.instance_foo.roles.peer.machines.jon ? settings; # settings are specific. @@ -88,10 +92,10 @@ in # roles = peer # machines = jon specificMachineSettings = - res.servicesEval.config.mappedServices.self-A.result.allMachines.jon.passthru.instances.instance_foo.roles.peer.machines.jon.settings; + res.config._services.mappedServices.self-A.result.allMachines.jon.passthru.instances.instance_foo.roles.peer.machines.jon.settings; hasRoleSettings = - res.servicesEval.config.mappedServices.self-A.result.allMachines.jon.passthru.instances.instance_foo.roles.peer + res.config._services.mappedServices.self-A.result.allMachines.jon.passthru.instances.instance_foo.roles.peer ? settings; # settings are specific. @@ -100,7 +104,7 @@ in # roles = peer # machines = * specificRoleSettings = - res.servicesEval.config.mappedServices.self-A.result.allMachines.jon.passthru.instances.instance_foo.roles.peer; + res.config._services.mappedServices.self-A.result.allMachines.jon.passthru.instances.instance_foo.roles.peer; }; expected = { hasMachineSettings = true; diff --git a/lib/inventory/distributed-service/tests/settings.nix b/lib/inventory/distributed-service/tests/settings.nix index f9214abda..422a529e7 100644 --- a/lib/inventory/distributed-service/tests/settings.nix +++ b/lib/inventory/distributed-service/tests/settings.nix @@ -1,6 +1,6 @@ -{ callInventoryAdapter, lib, ... }: +{ createTestClan, lib, ... }: let - res = callInventoryAdapter { + res = createTestClan { modules."A" = { _class = "clan.service"; manifest = { @@ -21,28 +21,31 @@ let }; }; }; - machines = { - jon = { }; - sara = { }; - }; - instances."instance_foo" = { - module = { - name = "A"; - input = "self"; + inventory = { + + machines = { + jon = { }; + sara = { }; }; - # Settings for both jon and sara - roles.peer.settings = { - timeout = 40; + instances."instance_foo" = { + module = { + name = "A"; + input = "self"; + }; + # Settings for both jon and sara + roles.peer.settings = { + timeout = 40; + }; + # Jon overrides timeout + roles.peer.machines.jon = { + settings.timeout = lib.mkForce 42; + }; + roles.peer.machines.sara = { }; }; - # Jon overrides timeout - roles.peer.machines.jon = { - settings.timeout = lib.mkForce 42; - }; - roles.peer.machines.sara = { }; }; }; - config = res.servicesEval.config.mappedServices.self-A; + config = res.config._services.mappedServices.self-A; # applySettings = diff --git a/lib/inventory/distributed-service/tests/specialArgs.nix b/lib/inventory/distributed-service/tests/specialArgs.nix index 8670f0e45..c29b266e5 100644 --- a/lib/inventory/distributed-service/tests/specialArgs.nix +++ b/lib/inventory/distributed-service/tests/specialArgs.nix @@ -1,6 +1,6 @@ -{ callInventoryAdapter, lib, ... }: +{ createTestClan, lib, ... }: let - res = callInventoryAdapter { + res = createTestClan { modules."A" = m: { _class = "clan.service"; config = { @@ -14,19 +14,21 @@ let default = m; }; }; - machines = { - jon = { }; - }; - instances."instance_foo" = { - module = { - name = "A"; - input = "self"; + inventory = { + machines = { + jon = { }; + }; + instances."instance_foo" = { + module = { + name = "A"; + input = "self"; + }; + roles.peer.machines.jon = { }; }; - roles.peer.machines.jon = { }; }; }; - specialArgs = lib.attrNames res.servicesEval.config.mappedServices.self-A.test.specialArgs; + specialArgs = lib.attrNames res.config._services.mappedServices.self-A.test.specialArgs; in { test_simple = { diff --git a/modules/clan/default.nix b/modules/clan/default.nix index f35ca8b2e..10392376a 100644 --- a/modules/clan/default.nix +++ b/modules/clan/default.nix @@ -19,6 +19,7 @@ imports = [ ./top-level-interface.nix ./module.nix + ./distributed-services.nix ./checks.nix ]; } diff --git a/modules/clan/distributed-services.nix b/modules/clan/distributed-services.nix new file mode 100644 index 000000000..adcff5cca --- /dev/null +++ b/modules/clan/distributed-services.nix @@ -0,0 +1,163 @@ +{ + lib, + clanLib, + config, + clan-core, + ... +}: +let + inherit (lib) mkOption types; + # Keep a reference to top-level + clanConfig = config; + + inventory = clanConfig.inventory; + flakeInputs = clanConfig.self.inputs; + clanCoreModules = clan-core.clan.modules; + + grouped = lib.foldlAttrs ( + acc: instanceName: instance: + let + inputName = if instance.module.input == null then "" else instance.module.input; + id = inputName + "-" + instance.module.name; + in + acc + // { + ${id} = acc.${id} or [ ] ++ [ + { + inherit instanceName instance; + } + ]; + } + ) { } importedModuleWithInstances; + + importedModuleWithInstances = lib.mapAttrs ( + instanceName: instance: + let + resolvedModule = clanLib.resolveModule { + moduleSpec = instance.module; + inherit flakeInputs clanCoreModules; + }; + + # Every instance includes machines via roles + # :: { client :: ... } + instanceRoles = lib.mapAttrs ( + roleName: role: + let + resolvedMachines = clanLib.inventory.resolveTags { + members = { + # Explicit members + machines = lib.attrNames role.machines; + # Resolved Members + tags = lib.attrNames role.tags; + }; + inherit (inventory) machines; + inherit instanceName roleName; + }; + in + # instances..roles. = + # Remove "tags", they are resolved into "machines" + (removeAttrs role [ "tags" ]) + // { + machines = lib.genAttrs resolvedMachines.machines ( + machineName: + let + machineSettings = instance.roles.${roleName}.machines.${machineName}.settings or { }; + 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}"; + settings = { + imports = [ + machineSettings + ]; # ++ lib.attrValues (lib.mapAttrs (_tagName: v: v.settings) settingsViaTags); + }; + } + ); + } + ) instance.roles; + in + { + inherit (instance) module; + inherit resolvedModule instanceRoles; + } + ) inventory.instances or { }; +in +{ + _class = "clan"; + options._services = mkOption { + visible = false; + description = '' + All service instances + + !!! Danger "Internal API" + Do not rely on this API yet. + + - Will be renamed to just 'services' in the future. + Once the name can be claimed again. + - Structure will change. + + API will be declared as public after beeing simplified. + ''; + type = types.submoduleWith { + # TODO: Remove specialArgs + specialArgs = { + inherit clanLib; + }; + modules = [ + (import ../../lib/inventory/distributed-service/all-services-wrapper.nix { + inherit (clanConfig) directory; + }) + # Dependencies + { + exportsModule = clanConfig.exportsModule; + } + { + # TODO: Rename to "allServices" + # All services + mappedServices = lib.mapAttrs (_module_ident: instances: { + imports = [ + # Import the resolved module. + # i.e. clan.modules.admin + { + options.module = lib.mkOption { + type = lib.types.raw; + default = (builtins.head instances).instance.module; + }; + } + (builtins.head instances).instance.resolvedModule + ] # Include all the instances that correlate to the resolved module + ++ (builtins.map (v: { + instances.${v.instanceName}.roles = v.instance.instanceRoles; + }) instances); + }) grouped; + } + ]; + }; + default = { }; + }; + options._allMachines = mkOption { + internal = true; + type = types.raw; + default = lib.mapAttrs (machineName: _: { + # This is the list of nixosModules for each machine + machineImports = lib.foldlAttrs ( + acc: _module_ident: serviceModule: + acc ++ [ serviceModule.result.final.${machineName}.nixosModule or { } ] + ) [ ] config._services.mappedServices; + }) inventory.machines or { }; + }; + + config = { + clanInternals.inventoryClass.machines = config._allMachines; + # clanInternals.inventoryClass.distributedServices = config._services; + + # Exports from distributed services + exports = config._services.exports; + }; +} diff --git a/modules/clan/eval-docs.nix b/modules/clan/eval-docs.nix index 68360723e..0e7d966fc 100644 --- a/modules/clan/eval-docs.nix +++ b/modules/clan/eval-docs.nix @@ -3,12 +3,16 @@ lib, clanModule, clanLib, + clan-core, }: let eval = lib.evalModules { modules = [ clanModule ]; + specialArgs = { + self = clan-core; + }; }; evalDocs = pkgs.nixosOptionsDoc { diff --git a/modules/clan/flake-module.nix b/modules/clan/flake-module.nix index a84dc554f..0178888ca 100644 --- a/modules/clan/flake-module.nix +++ b/modules/clan/flake-module.nix @@ -12,6 +12,7 @@ in }: let jsonDocs = import ./eval-docs.nix { + clan-core = self; inherit pkgs lib diff --git a/modules/clan/module.nix b/modules/clan/module.nix index ab433b68a..26739055c 100644 --- a/modules/clan/module.nix +++ b/modules/clan/module.nix @@ -219,8 +219,6 @@ in inherit nixosConfigurations; inherit darwinConfigurations; - exports = config.clanInternals.inventoryClass.distributedServices.servicesEval.config.exports; - clanInternals = { inventoryClass = let @@ -254,21 +252,9 @@ in exportsModule = config.exportsModule; } ( - { config, ... }: + { ... }: { staticModules = clan-core.clan.modules; - - distributedServices = clanLib.inventory.mapInstances { - inherit (config) - inventory - directory - flakeInputs - exportsModule - ; - clanCoreModules = clan-core.clan.modules; - prefix = [ "distributedServices" ]; - }; - machines = config.distributedServices.allMachines; } ) ]; diff --git a/modules/inventoryClass/default.nix b/modules/inventoryClass/default.nix index 20893aaa6..f355e9533 100644 --- a/modules/inventoryClass/default.nix +++ b/modules/inventoryClass/default.nix @@ -67,9 +67,6 @@ in type = types.raw; }; - distributedServices = mkOption { - type = types.raw; - }; inventory = mkOption { type = types.raw; }; diff --git a/pkgs/docs-from-code/flake-module.nix b/pkgs/docs-from-code/flake-module.nix index fd1dc1b4f..627721493 100644 --- a/pkgs/docs-from-code/flake-module.nix +++ b/pkgs/docs-from-code/flake-module.nix @@ -64,6 +64,9 @@ ''; in { + legacyPackages = { + inherit jsonDocs clanModulesViaService; + }; packages = { inherit module-docs; }; diff --git a/pkgs/option-search/flake-module.nix b/pkgs/option-search/flake-module.nix index 9e7628371..baadbdd82 100644 --- a/pkgs/option-search/flake-module.nix +++ b/pkgs/option-search/flake-module.nix @@ -11,151 +11,10 @@ ... }: let - inherit (lib) - mapAttrsToList - mapAttrs - mkOption - types - splitString - stringLength - substring - ; - inherit (self) clanLib; - - serviceModules = self.clan.modules; baseHref = "/option-search/"; - getRoles = - module: - (clanLib.evalService { - modules = [ module ]; - prefix = [ ]; - }).config.roles; - - getManifest = - module: - (clanLib.evalService { - modules = [ module ]; - prefix = [ ]; - }).config.manifest; - - settingsModules = module: mapAttrs (_roleName: roleConfig: roleConfig.interface) (getRoles module); - # Map each letter to its capitalized version - capitalizeChar = - char: - { - a = "A"; - b = "B"; - c = "C"; - d = "D"; - e = "E"; - f = "F"; - g = "G"; - h = "H"; - i = "I"; - j = "J"; - k = "K"; - l = "L"; - m = "M"; - n = "N"; - o = "O"; - p = "P"; - q = "Q"; - r = "R"; - s = "S"; - t = "T"; - u = "U"; - v = "V"; - w = "W"; - x = "X"; - y = "Y"; - z = "Z"; - } - .${char}; - - title = - name: - let - # split by - - parts = splitString "-" name; - # capitalize first letter of each part - capitalize = part: (capitalizeChar (substring 0 1 part)) + substring 1 (stringLength part) part; - capitalizedParts = map capitalize parts; - in - builtins.concatStringsSep " " capitalizedParts; - - fakeInstanceOptions = - name: module: - let - manifest = getManifest module; - description = '' - # ${title name} (Clan Service) - - **${manifest.description}** - - ${lib.optionalString (manifest ? readme) manifest.readme} - - ${ - if manifest.categories != [ ] then - "Categories: " + builtins.concatStringsSep ", " manifest.categories - else - "No categories defined" - } - - ''; - in - { - options = { - instances.${name} = lib.mkOption { - inherit description; - type = types.submodule { - options.roles = mapAttrs ( - roleName: roleSettingsModule: - mkOption { - type = types.submodule { - _file = "docs flake-module"; - imports = [ - { _module.args = { inherit clanLib; }; } - (import ../../modules/inventoryClass/role.nix { - nestedSettingsOption = mkOption { - type = types.raw; - description = '' - See [instances.${name}.roles.${roleName}.settings](${baseHref}?option_scope=0&option=inventory.instances.${name}.roles.${roleName}.settings) - ''; - }; - settingsOption = mkOption { - type = types.submoduleWith { - modules = [ roleSettingsModule ]; - }; - }; - }) - ]; - }; - } - ) (settingsModules module); - }; - }; - }; - }; - - docModules = [ - { - inherit self; - } - self.modules.clan.default - { - options.inventory = lib.mkOption { - type = types.submoduleWith { - modules = [ - { noInstanceOptions = true; } - ] - ++ mapAttrsToList fakeInstanceOptions serviceModules; - }; - }; - } - ]; baseModule = # Module @@ -208,12 +67,6 @@ title = "Clan Options"; # scopes = mapAttrsToList mkScope serviceModules; scopes = [ - { - inherit baseHref; - name = "Flake Options (clan.nix file)"; - modules = docModules; - urlPrefix = "https://git.clan.lol/clan/clan-core/src/branch/main/"; - } { name = "Machine Options (clan.core NixOS options)"; optionsJSON = "${coreOptions}/share/doc/nixos/options.json";