From 308a10d6e61b424ccecf865bb94ee943863617f9 Mon Sep 17 00:00:00 2001 From: Johannes Kirschbauer Date: Sat, 16 Aug 2025 11:48:13 +0200 Subject: [PATCH] clanModules: remove unused code --- checks/flake-module.nix | 27 -- docs/nix/flake-module.nix | 11 - docs/nix/get-module-docs.nix | 14 - docs/nix/render_options/__init__.py | 158 ----------- flake.nix | 1 - .../inventory/eval-clan-modules/default.nix | 49 ---- lib/modules/inventory/frontmatter/default.nix | 50 ---- lib/modules/inventory/schemas/default.nix | 35 --- .../inventory/schemas/render_schema.py | 162 ----------- lib/modules/inventoryClass/assertions.nix | 78 ------ .../inventoryClass/builder/default.nix | 263 ------------------ lib/modules/inventoryClass/builder/roles.nix | 65 ----- lib/modules/inventoryClass/interface.nix | 160 +---------- .../inventory-introspection.nix | 3 +- 14 files changed, 6 insertions(+), 1070 deletions(-) delete mode 100644 lib/modules/inventory/schemas/render_schema.py delete mode 100644 lib/modules/inventoryClass/assertions.nix delete mode 100644 lib/modules/inventoryClass/builder/roles.nix diff --git a/checks/flake-module.nix b/checks/flake-module.nix index a3dedf0ec..c8d703060 100644 --- a/checks/flake-module.nix +++ b/checks/flake-module.nix @@ -139,33 +139,6 @@ in nixosTests // flakeOutputs // { - # TODO: Automatically provide this check to downstream users to check their modules - clan-modules-json-compatible = - let - allSchemas = lib.mapAttrs ( - _n: m: - let - schema = - (self.clanLib.evalService { - modules = [ m ]; - prefix = [ - "checks" - system - ]; - }).config.result.api.schema; - in - schema - ) self.clan.modules; - in - pkgs.runCommand "combined-result" - { - schemaFile = builtins.toFile "schemas.json" (builtins.toJSON allSchemas); - } - '' - mkdir -p $out - cat $schemaFile > $out/allSchemas.json - ''; - clan-core-for-checks = pkgs.runCommand "clan-core-for-checks" { } '' cp -r ${privateInputs.clan-core-for-checks} $out chmod -R +w $out diff --git a/docs/nix/flake-module.nix b/docs/nix/flake-module.nix index 17467463a..22c6c07af 100644 --- a/docs/nix/flake-module.nix +++ b/docs/nix/flake-module.nix @@ -19,11 +19,6 @@ clan-core = self; inherit pkgs; evalClanModules = self.clanLib.evalClan.evalClanModules; - modulesRolesOptions = self.clanLib.evalClan.evalClanModulesWithRoles { - allModules = self.clanModules; - inherit pkgs; - clan-core = self; - }; }; # Frontmatter for clanModules @@ -35,10 +30,6 @@ }; in docs.optionsJSON; - - # Options available when imported via ` inventory.${moduleName}....${rolesName} ` - clanModulesViaRoles = pkgs.writeText "info.json" (builtins.toJSON jsonDocs.clanModulesViaRoles); - # clan service options clanModulesViaService = pkgs.writeText "info.json" (builtins.toJSON jsonDocs.clanModulesViaService); @@ -89,7 +80,6 @@ } export CLAN_CORE_DOCS=${jsonDocs.clanCore}/share/doc/nixos/options.json # A file that contains the links to all clanModule docs - export CLAN_MODULES_VIA_ROLES=${clanModulesViaRoles} export CLAN_MODULES_VIA_SERVICE=${clanModulesViaService} export CLAN_SERVICE_INTERFACE=${self'.legacyPackages.clan-service-module-interface}/share/doc/nixos/options.json # Frontmatter format for clanModules @@ -107,7 +97,6 @@ legacyPackages = { inherit jsonDocs - clanModulesViaRoles clanModulesViaService ; }; diff --git a/docs/nix/get-module-docs.nix b/docs/nix/get-module-docs.nix index a58ce69ff..d736744f7 100644 --- a/docs/nix/get-module-docs.nix +++ b/docs/nix/get-module-docs.nix @@ -1,5 +1,4 @@ { - modulesRolesOptions, nixosOptionsDoc, evalClanModules, lib, @@ -12,19 +11,6 @@ let transformOptions = stripStorePathsFromDeclarations; in { - - clanModulesViaRoles = lib.mapAttrs ( - _moduleName: rolesOptions: - lib.mapAttrs ( - _roleName: options: - (nixosOptionsDoc { - inherit options; - warningsAreErrors = true; - inherit transformOptions; - }).optionsJSON - ) rolesOptions - ) modulesRolesOptions; - # Test with: # nix build .\#legacyPackages.x86_64-linux.clanModulesViaService clanModulesViaService = lib.mapAttrs ( diff --git a/docs/nix/render_options/__init__.py b/docs/nix/render_options/__init__.py index d0327461f..acbad9ed5 100644 --- a/docs/nix/render_options/__init__.py +++ b/docs/nix/render_options/__init__.py @@ -33,8 +33,6 @@ from clan_lib.errors import ClanError from clan_lib.services.modules import ( CategoryInfo, Frontmatter, - extract_frontmatter, - get_roles, ) # Get environment variables @@ -43,12 +41,6 @@ CLAN_CORE_DOCS = Path(os.environ["CLAN_CORE_DOCS"]) CLAN_MODULES_FRONTMATTER_DOCS = os.environ.get("CLAN_MODULES_FRONTMATTER_DOCS") BUILD_CLAN_PATH = os.environ.get("BUILD_CLAN_PATH") -## Clan modules ## -# Some modules can be imported via nix natively -CLAN_MODULES_VIA_NIX = os.environ.get("CLAN_MODULES_VIA_NIX") -# Some modules can be imported via inventory -CLAN_MODULES_VIA_ROLES = os.environ.get("CLAN_MODULES_VIA_ROLES") - # Options how to author clan.modules # perInstance, perMachine, ... CLAN_SERVICE_INTERFACE = os.environ.get("CLAN_SERVICE_INTERFACE") @@ -505,154 +497,6 @@ Learn how to use `clanServices` in practice in the [Using clanServices guide](.. of.write(output) -def produce_clan_modules_docs() -> None: - if not CLAN_MODULES_VIA_NIX: - msg = f"Environment variables are not set correctly: $CLAN_MODULES_VIA_NIX={CLAN_MODULES_VIA_NIX}" - raise ClanError(msg) - - if not CLAN_MODULES_VIA_ROLES: - msg = f"Environment variables are not set correctly: $CLAN_MODULES_VIA_ROLES={CLAN_MODULES_VIA_ROLES}" - raise ClanError(msg) - - if not CLAN_CORE_PATH: - msg = f"Environment variables are not set correctly: $CLAN_CORE_PATH={CLAN_CORE_PATH}" - raise ClanError(msg) - - if not OUT: - msg = f"Environment variables are not set correctly: $out={OUT}" - raise ClanError(msg) - - modules_index = "# Modules Overview\n\n" - modules_index += clan_modules_descr - modules_index += "## Overview\n\n" - modules_index += '
\n\n' - - with Path(CLAN_MODULES_VIA_ROLES).open() as f2: - role_links: dict[str, dict[str, str]] = json.load(f2) - - with Path(CLAN_MODULES_VIA_NIX).open() as f: - links: dict[str, str] = json.load(f) - - for module_name, options_file in links.items(): - print(f"Rendering ClanModule: {module_name}") - readme_file = CLAN_CORE_PATH / "clanModules" / module_name / "README.md" - with readme_file.open() as f: - readme = f.read() - frontmatter: Frontmatter - frontmatter, readme_content = extract_frontmatter(readme, str(readme_file)) - - # skip if experimental feature enabled - if "experimental" in frontmatter.features: - print(f"Skipping {module_name}: Experimental feature") - continue - - modules_index += build_option_card(module_name, frontmatter) - - ##### Print module documentation ##### - - # 1. Header - output = module_header(module_name, "inventory" in frontmatter.features) - - # 2. Description from README.md - if frontmatter.description: - output += f"*{frontmatter.description}*\n\n" - - # 2. Deprecation note if the module is deprecated - if "deprecated" in frontmatter.features: - output += f""" -!!! Warning "Deprecated" - The `{module_name}` module is deprecated.* - - Use 'clanServices/{module_name}' or a similar successor instead -""" - else: - output += f""" -!!! Warning "Will be deprecated" - The `{module_name}` module might eventually be migrated to 'clanServices'* - - See: [clanServices](../../guides/clanServices.md) -""" - - # 3. Categories from README.md - output += "## Categories\n\n" - output += render_categories(frontmatter.categories, frontmatter.categories_info) - output += "\n---\n\n" - - # 3. README.md content - output += f"{readme_content}\n" - - # 4. Usage - ##### Print usage via Inventory ##### - - # get_roles(str) -> list[str] | None - # if not isinstance(options_file, str): - roles = get_roles(CLAN_CORE_PATH / "clanModules" / module_name) - if roles: - # Render inventory usage - output += """## Usage via Inventory\n\n""" - output += render_roles(roles, module_name) - for role in roles: - role_options_file = role_links[module_name][role] - # Abort if the options file is not found - if not isinstance(role_options_file, str): - print( - f"Error: module: {module_name} in role: {role} - options file not found, Got {role_options_file}" - ) - exit(1) - - no_options = f"""### Options of `{role}` role - -**The `{module_name}` `{role}` doesnt offer / require any options to be set.** -""" - - heading = f"""### Options of `{role}` role - -The following options are available when using the `{role}` role. -""" - output += print_options( - role_options_file, - heading, - no_options, - replace_prefix=f"clan.{module_name}", - ) - else: - # No roles means no inventory usage - output += """## Usage via Inventory - -**This module cannot be used via the inventory interface.** -""" - - ##### Print usage via Nix / nixos ##### - if not isinstance(options_file, str): - print( - f"Skipping {module_name}: Cannot be used via import clanModules.{module_name}" - ) - output += """## Usage via Nix - -**This module cannot be imported directly in your nixos configuration.** - -""" - else: - output += module_nix_usage(module_name) - no_options = "** This module doesnt require any options to be set.**" - output += print_options(options_file, options_head, no_options) - - outfile = Path(OUT) / f"clanModules/{module_name}.md" - outfile.parent.mkdir( - parents=True, - exist_ok=True, - ) - with outfile.open("w") as of: - of.write(output) - - modules_index += "
" - modules_index += "\n" - modules_outfile = Path(OUT) / "clanModules/index.md" - - with modules_outfile.open("w") as of: - of.write(modules_index) - - def build_option_card(module_name: str, frontmatter: Frontmatter) -> str: """ Build the overview index card for each reference target option. @@ -863,8 +707,6 @@ if __name__ == "__main__": # produce_clan_core_docs() produce_clan_service_author_docs() - - # produce_clan_modules_docs() produce_clan_service_docs() # produce_clan_modules_frontmatter_docs() diff --git a/flake.nix b/flake.nix index 15406c1cd..7e92e3ec1 100644 --- a/flake.nix +++ b/flake.nix @@ -67,7 +67,6 @@ clan = { meta.name = "clan-core"; inventory = { - services = { }; machines = { "test-darwin-machine" = { machineClass = "darwin"; diff --git a/lib/modules/inventory/eval-clan-modules/default.nix b/lib/modules/inventory/eval-clan-modules/default.nix index bc836761c..f356ddc98 100644 --- a/lib/modules/inventory/eval-clan-modules/default.nix +++ b/lib/modules/inventory/eval-clan-modules/default.nix @@ -52,57 +52,8 @@ let # '' evaled; - /* - This function takes a list of module names and evaluates them - Returns a set of interfaces as described below: - Fn :: { ${moduleName} = Module; } -> { - ${moduleName} :: { - ${roleName}: JSONSchema - } - } - */ - evalClanModulesWithRoles = - { - allModules, - clan-core, - pkgs, - }: - let - res = builtins.mapAttrs ( - moduleName: module: - let - frontmatter = clanLib.modules.getFrontmatter allModules.${moduleName} moduleName; - roles = - if builtins.elem "inventory" frontmatter.features or [ ] then - assert lib.isPath module; - clan-core.clanLib.modules.getRoles "Documentation: inventory.modules" allModules moduleName - else - [ ]; - in - lib.listToAttrs ( - lib.map (role: { - name = role; - value = - (lib.evalModules { - class = "nixos"; - modules = [ - (baseModule { inherit pkgs; }) - clan-core.nixosModules.clanCore - { - clan.core.settings.directory = clan-core; - } - # Role interface - (module + "/roles/${role}.nix") - ]; - }).options.clan.${moduleName} or { }; - }) roles - ) - ) allModules; - in - res; in { evalClanModules = evalClanModulesLegacy; - inherit evalClanModulesWithRoles; } diff --git a/lib/modules/inventory/frontmatter/default.nix b/lib/modules/inventory/frontmatter/default.nix index 1ce0c656a..9084bbef1 100644 --- a/lib/modules/inventory/frontmatter/default.nix +++ b/lib/modules/inventory/frontmatter/default.nix @@ -3,51 +3,6 @@ let # Trim the .nix extension from a filename trimExtension = name: builtins.substring 0 (builtins.stringLength name - 4) name; - jsonWithoutHeader = clanLib.jsonschema { - includeDefaults = true; - header = { }; - }; - - getModulesSchema = - { - modules, - clan-core, - pkgs, - }: - lib.mapAttrs - ( - _moduleName: rolesOptions: - lib.mapAttrs (_roleName: options: jsonWithoutHeader.parseOptions options { }) rolesOptions - ) - ( - clanLib.evalClan.evalClanModulesWithRoles { - allModules = modules; - inherit pkgs clan-core; - } - ); - - evalFrontmatter = - { - moduleName, - instanceName, - resolvedRoles, - allModules, - }: - lib.evalModules { - modules = [ - (getFrontmatter allModules.${moduleName} moduleName) - ./interface.nix - { - constraints.imports = [ - (lib.modules.importApply ../constraints { - inherit moduleName resolvedRoles instanceName; - allRoles = getRoles "inventory.modules" allModules moduleName; - }) - ]; - } - ]; - }; - # For Documentation purposes only frontmatterOptions = (lib.evalModules { @@ -119,17 +74,12 @@ let builtins.readDir (checkedPath) ) ); - - checkConstraints = args: (evalFrontmatter args).config.constraints.assertions; getFrontmatter = _modulepath: _modulename: "clanModules are removed!"; in { inherit frontmatterOptions - getModulesSchema getFrontmatter - - checkConstraints getRoles ; } diff --git a/lib/modules/inventory/schemas/default.nix b/lib/modules/inventory/schemas/default.nix index a7e92f589..946e64f7e 100644 --- a/lib/modules/inventory/schemas/default.nix +++ b/lib/modules/inventory/schemas/default.nix @@ -1,19 +1,11 @@ { self, - self', lib, pkgs, flakeOptions, ... }: let - - modulesSchema = self.clanLib.modules.getModulesSchema { - modules = self.clanModules; - inherit pkgs; - clan-core = self; - }; - jsonLib = self.clanLib.jsonschema { inherit includeDefaults; }; includeDefaults = true; @@ -38,13 +30,6 @@ let ]; clanSchema = jsonLib.parseOptions (lib.filterAttrs (n: _v: lib.elem n include) clanOpts) { }; - renderSchema = pkgs.writers.writePython3Bin "render-schema" { - flakeIgnore = [ - "F401" - "E501" - ]; - } ./render_schema.py; - clan-schema-abstract = pkgs.stdenv.mkDerivation { name = "clan-schema-files"; buildInputs = [ pkgs.cue ]; @@ -66,26 +51,6 @@ in frontMatterSchema clanSchema inventorySchema - modulesSchema - renderSchema clan-schema-abstract ; - - # Inventory schema, with the modules schema added per role - inventory = - pkgs.runCommand "rendered" - { - buildInputs = [ - pkgs.python3 - self'.packages.clan-cli - ]; - } - '' - export INVENTORY_SCHEMA_PATH=${builtins.toFile "inventory-schema.json" (builtins.toJSON inventorySchema)} - export MODULES_SCHEMA_PATH=${builtins.toFile "modules-schema.json" (builtins.toJSON modulesSchema)} - - mkdir $out - # The python script will place the schemas in the output directory - exec python3 ${renderSchema}/bin/render-schema - ''; } diff --git a/lib/modules/inventory/schemas/render_schema.py b/lib/modules/inventory/schemas/render_schema.py deleted file mode 100644 index de544049a..000000000 --- a/lib/modules/inventory/schemas/render_schema.py +++ /dev/null @@ -1,162 +0,0 @@ -""" -Python script to join the abstract inventory schema, with the concrete clan modules -Inventory has slots which are 'Any' type. -We dont want to evaluate the clanModules interface in nix, when evaluating the inventory -""" - -import json -import os -from pathlib import Path -from typing import Any - -from clan_lib.errors import ClanError - -# Get environment variables -INVENTORY_SCHEMA_PATH = Path(os.environ["INVENTORY_SCHEMA_PATH"]) - -# { [moduleName] :: { [roleName] :: SCHEMA }} -MODULES_SCHEMA_PATH = Path(os.environ["MODULES_SCHEMA_PATH"]) - -OUT = os.environ.get("out") - -if not INVENTORY_SCHEMA_PATH: - msg = f"Environment variables are not set correctly: INVENTORY_SCHEMA_PATH={INVENTORY_SCHEMA_PATH}." - raise ClanError(msg) - -if not MODULES_SCHEMA_PATH: - msg = f"Environment variables are not set correctly: MODULES_SCHEMA_PATH={MODULES_SCHEMA_PATH}." - raise ClanError(msg) - -if not OUT: - msg = f"Environment variables are not set correctly: OUT={OUT}." - raise ClanError(msg) - - -def service_roles_to_schema( - schema: dict[str, Any], - service_name: str, - roles: list[str], - roles_schemas: dict[str, dict[str, Any]], - # Original service properties: {'config': Schema, 'machines': Schema, 'meta': Schema, 'extraModules': Schema, ...?} - orig: dict[str, Any], -) -> dict[str, Any]: - """ - Add roles to the service schema - """ - # collect all the roles for the service, to form a type union - all_roles_schema: list[dict[str, Any]] = [] - for role_name, role_schema in roles_schemas.items(): - role_schema["title"] = f"{module_name}-config-role-{role_name}" - all_roles_schema.append(role_schema) - - role_schema = {} - for role in roles: - role_schema[role] = { - "type": "object", - "additionalProperties": False, - "properties": { - **orig["roles"]["additionalProperties"]["properties"], - "config": { - **roles_schemas.get(role, {}), - "title": f"{service_name}-config-role-{role}", - "type": "object", - "default": {}, - "additionalProperties": False, - }, - }, - } - - machines_schema = { - "type": "object", - "additionalProperties": { - "type": "object", - "properties": { - **orig["machines"]["additionalProperties"]["properties"], - "config": { - "title": f"{service_name}-config", - "oneOf": all_roles_schema, - "type": "object", - "default": {}, - "additionalProperties": False, - }, - }, - }, - } - - services["properties"][service_name] = { - "type": "object", - "additionalProperties": { - "type": "object", - "additionalProperties": False, - "properties": { - # Original inventory schema - **orig, - # Inject the roles schemas - "roles": { - "title": f"{service_name}-roles", - "type": "object", - "properties": role_schema, - "additionalProperties": False, - }, - "machines": machines_schema, - "config": { - "title": f"{service_name}-config", - "oneOf": all_roles_schema, - "type": "object", - "default": {}, - "additionalProperties": False, - }, - }, - }, - } - - return schema - - -if __name__ == "__main__": - print("Joining inventory schema with modules schema") - print(f"Inventory schema path: {INVENTORY_SCHEMA_PATH}") - print(f"Modules schema path: {MODULES_SCHEMA_PATH}") - - modules_schema = {} - with Path.open(MODULES_SCHEMA_PATH) as f: - modules_schema = json.load(f) - - inventory_schema = {} - with Path.open(INVENTORY_SCHEMA_PATH) as f: - inventory_schema = json.load(f) - - services = inventory_schema["properties"]["services"] - original_service_props = services["additionalProperties"]["additionalProperties"][ - "properties" - ].copy() - # Init the outer services schema - # Properties (service names) will be filled in the next step - services = { - "type": "object", - "properties": { - # Service names - }, - "additionalProperties": False, - } - - for module_name, roles_schemas in modules_schema.items(): - # Add the roles schemas to the service schema - roles = list(roles_schemas.keys()) - if roles: - services = service_roles_to_schema( - services, - module_name, - roles, - roles_schemas, - original_service_props, - ) - - inventory_schema["properties"]["services"] = services - - outpath = Path(OUT) - with (outpath / "schema.json").open("w") as f: - json.dump(inventory_schema, f, indent=2) - - with (outpath / "modules_schemas.json").open("w") as f: - json.dump(modules_schema, f, indent=2) diff --git a/lib/modules/inventoryClass/assertions.nix b/lib/modules/inventoryClass/assertions.nix deleted file mode 100644 index 5326f4a74..000000000 --- a/lib/modules/inventoryClass/assertions.nix +++ /dev/null @@ -1,78 +0,0 @@ -# Integrity validation of the inventory -{ config, lib, ... }: -{ - # Assertion must be of type - # { assertion :: bool, message :: string, severity :: "error" | "warning" } - imports = [ - # Check that each machine used in a service is defined in the top-level machines - { - assertions = lib.foldlAttrs ( - ass1: serviceName: c: - ass1 - ++ lib.foldlAttrs ( - ass2: instanceName: instanceConfig: - let - topLevelMachines = lib.attrNames config.machines; - # All machines must be defined in the top-level machines - assertions = lib.foldlAttrs ( - assertions: roleName: role: - assertions - ++ builtins.filter (a: !a.assertion) ( - builtins.map (m: { - assertion = builtins.elem m topLevelMachines; - message = '' - Machine '${m}' is not defined in the inventory. This might still work, if the machine is defined via nix. - - Defined in service: '${serviceName}' instance: '${instanceName}' role: '${roleName}'. - - Inventory machines: - ${builtins.concatStringsSep "\n" (map (n: "'${n}'") topLevelMachines)} - ''; - severity = "warning"; - }) role.machines - ) - ) [ ] instanceConfig.roles; - in - ass2 ++ assertions - ) [ ] c - ) [ ] config.services; - } - # Check that each tag used in a role is defined in at least one machines tags - { - assertions = lib.foldlAttrs ( - ass1: serviceName: c: - ass1 - ++ lib.foldlAttrs ( - ass2: instanceName: instanceConfig: - let - allTags = lib.foldlAttrs ( - tags: _machineName: machine: - tags ++ machine.tags - ) [ ] config.machines; - # All machines must be defined in the top-level machines - assertions = lib.foldlAttrs ( - assertions: roleName: role: - assertions - ++ builtins.filter (a: !a.assertion) ( - builtins.map (m: { - assertion = builtins.elem m allTags; - message = '' - Tag '${m}' is not defined in the inventory. - - Defined in service: '${serviceName}' instance: '${instanceName}' role: '${roleName}'. - - Available tags: - ${builtins.concatStringsSep "\n" (map (n: "'${n}'") allTags)} - ''; - severity = "error"; - }) role.tags - ) - ) [ ] instanceConfig.roles; - in - ass2 ++ assertions - ) [ ] c - ) [ ] config.services; - } - ]; - -} diff --git a/lib/modules/inventoryClass/builder/default.nix b/lib/modules/inventoryClass/builder/default.nix index c25ea7057..bd4ca0b59 100644 --- a/lib/modules/inventoryClass/builder/default.nix +++ b/lib/modules/inventoryClass/builder/default.nix @@ -1,268 +1,5 @@ -{ - lib, - config, - clanLib, - ... -}: -let - inherit (config) inventory directory; - resolveTags = - # Inventory, { machines :: [string], tags :: [string] } - { - serviceName, - instanceName, - roleName, - inventory, - members, - }: - { - machines = - members.machines or [ ] - ++ (builtins.foldl' ( - acc: tag: - let - # For error printing - availableTags = lib.foldlAttrs ( - acc: _: v: - v.tags or [ ] ++ acc - ) [ ] (inventory.machines); - - tagMembers = builtins.attrNames ( - lib.filterAttrs (_n: v: builtins.elem tag v.tags or [ ]) inventory.machines - ); - in - if tagMembers == [ ] then - lib.warn '' - inventory.services.${serviceName}.${instanceName}: - ${roleName} tags: no machine with tag '${tag}' found. - Available tags: ${builtins.toJSON (lib.unique availableTags)} - '' [ ] - else - acc ++ tagMembers - ) [ ] members.tags or [ ]); - }; - - checkService = - modulepath: serviceName: - builtins.elem "inventory" (clanLib.modules.getFrontmatter modulepath serviceName).features or [ ]; - - compileMachine = - { machineConfig }: - { - machineImports = [ - (lib.optionalAttrs (machineConfig.deploy.targetHost or null != null) { - config.clan.core.networking.targetHost = lib.mkForce machineConfig.deploy.targetHost; - }) - (lib.optionalAttrs (machineConfig.deploy.buildHost or null != null) { - config.clan.core.networking.buildHost = lib.mkForce machineConfig.deploy.buildHost; - }) - ]; - assertions = { }; - }; - - resolveImports = - { - supportedRoles, - resolvedRolesPerInstance, - serviceConfigs, - serviceName, - machineName, - getRoleFile, - }: - (lib.foldlAttrs ( - # : [ Modules ] -> String -> ServiceConfig -> [ Modules ] - acc2: instanceName: serviceConfig: - let - resolvedRoles = resolvedRolesPerInstance.${instanceName}; - - 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 don't lookup the role in inverse roles. Imports are not lazy - roleModules = builtins.map ( - role: - if builtins.elem role supportedRoles && inventory.modules ? ${serviceName} then - getRoleFile role - 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 - ); - - extraModules = map (s: if builtins.typeOf s == "string" then "${directory}/${s}" else s) ( - globalExtraModules ++ machineExtraModules ++ roleServiceExtraModules - ); - - features = - (clanLib.modules.getFrontmatter inventory.modules.${serviceName} serviceName).features or [ ]; - deprecationWarning = lib.optionalAttrs (builtins.elem "deprecated" features) { - warnings = [ - '' - The '${serviceName}' module has been migrated from `inventory.services` to `inventory.instances` - See https://docs.clan.lol/guides/clanServices/ for usage. - '' - ]; - }; - in - if !(serviceConfig.enabled or true) then - acc2 - else if isInService then - acc2 - ++ [ - deprecationWarning - { - imports = roleModules ++ extraModules; - 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)); -in { imports = [ ./interface.nix ]; - config = { - machines = builtins.mapAttrs ( - machineName: machineConfig: m: - let - compiledServices = lib.mapAttrs ( - _: serviceConfigs: - ( - { config, ... }: - let - serviceName = config.serviceName; - - getRoleFile = role: builtins.seq role inventory.modules.${serviceName} + "/roles/${role}.nix"; - in - { - _file = "inventory/builder.nix"; - _module.args = { - inherit - resolveTags - inventory - clanLib - machineName - serviceConfigs - ; - }; - imports = [ - ./roles.nix - ]; - - machineImports = resolveImports { - supportedRoles = config.supportedRoles; - resolvedRolesPerInstance = config.resolvedRolesPerInstance; - inherit - serviceConfigs - serviceName - machineName - getRoleFile - ; - }; - - # Assertions - assertions = { - "checkservice.${serviceName}" = { - 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. - - ''; - }; - }; - } - ) - ) (config.inventory.services or { }); - - compiledMachine = compileMachine { - inherit - machineConfig - ; - }; - - machineImports = ( - compiledMachine.machineImports - ++ builtins.foldl' ( - acc: service: - let - failedAssertions = (lib.filterAttrs (_: v: !v.assertion) service.assertions); - failedAssertionsImports = - if failedAssertions != { } then - [ - { - clan.inventory.assertions = failedAssertions; - } - ] - 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 - acc - ++ service.machineImports - # Import failed assertions - ++ failedAssertionsImports - ) [ ] (builtins.attrValues m.config.compiledServices) - ); - in - { - inherit machineImports compiledServices compiledMachine; - } - ) (inventory.machines or { }); - }; } diff --git a/lib/modules/inventoryClass/builder/roles.nix b/lib/modules/inventoryClass/builder/roles.nix deleted file mode 100644 index 364b72d62..000000000 --- a/lib/modules/inventoryClass/builder/roles.nix +++ /dev/null @@ -1,65 +0,0 @@ -{ - lib, - config, - resolveTags, - inventory, - clanLib, - machineName, - serviceConfigs, - ... -}: -let - serviceName = config.serviceName; -in -{ - # Roles resolution - # : List String - supportedRoles = clanLib.modules.getRoles "inventory.modules" 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: - clanLib.modules.checkConstraints { - moduleName = serviceName; - allModules = inventory.modules; - inherit resolvedRoles instanceName; - } - ) config.resolvedRolesPerInstance; -} diff --git a/lib/modules/inventoryClass/interface.nix b/lib/modules/inventoryClass/interface.nix index b66c216de..25fd4a08e 100644 --- a/lib/modules/inventoryClass/interface.nix +++ b/lib/modules/inventoryClass/interface.nix @@ -94,7 +94,10 @@ let in { imports = [ - ./assertions.nix + (lib.mkRemovedOptionModule [ "services" ] '' + The `inventory.services` option has been removed. Use `inventory.instances` instead. + See: https://docs.clan.lol/concepts/inventory/#services + '') ]; options = { # Internal things @@ -415,160 +418,5 @@ in ); default = { }; }; - - services = lib.mkOption { - # TODO: deprecate these options - # services are deprecated in favor of `instances` - # visible = false; - description = '' - Services of the inventory. - - - The first `` is the moduleName. It must be a valid clanModule name. - - The second `` is an arbitrary instance name. - - ???+ Example - ```nix - # ClanModule name. See the module documentation for the available modules. - # ↓ ↓ Instance name, can be anything, some services might use it as a unique identifier. - services.borgbackup."instance_1" = { - roles.client.machines = ["machineA"]; - }; - ``` - - !!! Note - Services MUST be added to machines via `roles` exclusively. - See [`roles..machines`](#inventory.services.roles.machines) or [`roles..tags`](#inventory.services.roles.tags) for more information. - ''; - default = { }; - type = types.attrsOf ( - types.attrsOf ( - types.submodule ( - # instance name - { name, ... }: - { - options.enabled = lib.mkOption { - type = lib.types.bool; - default = true; - description = '' - Enable or disable the complete service. - - If the service is disabled, it will not be added to any machine. - - !!! Note - This flag is primarily used to temporarily disable a service. - I.e. A 'backup service' without any 'server' might be incomplete and would cause failure if enabled. - ''; - }; - options.meta = metaOptionsWith name; - options.extraModules = extraModulesOption; - options.config = moduleConfig // { - description = '' - Configuration of the specific clanModule. - - !!! Note - Configuration is passed to the nixos configuration scoped to the module. - - ```nix - clan. = { ... # Config } - ``` - - ???+ Example - - For `services.borgbackup` the config is the passed to the machine with the prefix of `clan.borgbackup`. - This means all config values are mapped to the `borgbackup` clanModule exclusively (`config.clan.borgbackup`). - - ```nix - { - services.borgbackup."instance_1".config = { - destinations = [ ... ]; - # See the 'borgbackup' module docs for all options - }; - } - ``` - - !!! Note - The module author is responsible for supporting multiple instance configurations in different roles. - See each clanModule's documentation for more information. - ''; - }; - options.machines = lib.mkOption { - description = '' - Attribute set of machines specific config for the service. - - Will be merged with other service configs, such as the role config and the global config. - For machine specific overrides use `mkForce` or other higher priority methods. - - ???+ Example - - ```{.nix hl_lines="4-7"} - services.borgbackup."instance_1" = { - roles.client.machines = ["machineA"]; - - machines.machineA.config = { - # Additional specific config for the machine - # This is merged with all other config places - }; - }; - ``` - ''; - default = { }; - type = types.attrsOf ( - types.submodule { - options.extraModules = extraModulesOption; - options.config = moduleConfig // { - description = '' - Additional configuration of the specific machine. - - See how [`service...config`](#inventory.services.config) works in general for further information. - ''; - }; - } - ); - }; - options.roles = lib.mkOption { - default = { }; - type = types.attrsOf ( - types.submodule { - options.machines = lib.mkOption { - default = [ ]; - type = types.listOf types.str; - example = [ "machineA" ]; - description = '' - List of machines which are part of the role. - - The machines are referenced by their `attributeName` in the `inventory.machines` attribute set. - - Memberships are declared here to determine which machines are part of the service. - - Alternatively, `tags` can be used to determine the membership, more dynamically. - ''; - }; - options.tags = lib.mkOption { - default = [ ]; - apply = lib.unique; - type = types.listOf types.str; - description = '' - List of tags which are used to determine the membership of the role. - - The tags are matched against the `inventory.machines..tags` attribute set. - If a machine has at least one tag of the role, it is part of the role. - ''; - }; - options.config = moduleConfig // { - description = '' - Additional configuration of the specific role. - - See how [`service...config`](#inventory.services.config) works in general for further information. - ''; - }; - options.extraModules = extraModulesOption; - } - ); - }; - } - ) - ) - ); - }; }; } diff --git a/lib/modules/inventoryClass/inventory-introspection.nix b/lib/modules/inventoryClass/inventory-introspection.nix index 7ff95af12..a9b77bd32 100644 --- a/lib/modules/inventoryClass/inventory-introspection.nix +++ b/lib/modules/inventoryClass/inventory-introspection.nix @@ -11,6 +11,7 @@ default = builtins.removeAttrs (clanLib.introspection.getPrios { options = config.inventory.options; }) # tags are freeformType which is not supported yet. - [ "tags" ]; + # services is removed and throws an error if accessed. + [ "tags" "services"]; }; }