diff --git a/docs/site/clanmodules/index.md b/docs/site/clanmodules/index.md index 0d650f979..46bd76ec7 100644 --- a/docs/site/clanmodules/index.md +++ b/docs/site/clanmodules/index.md @@ -41,6 +41,8 @@ clanModules/borgbackup The `roles` folder is strictly required for `features = [ "inventory" ]`. +## Registering the module + === "User module" If the module should be ad-hoc loaded. @@ -134,6 +136,56 @@ Adds the roles: `client` and `server` } ``` +## Adding configuration options + +While we recommend to keep the interface as minimal as possible and deriving all required information from the `roles` model it might sometimes be required or convinient to expose customization options beyond `roles`. + +The following shows how to add options to your module. + +**It is important to understand that every module has its own namespace where it should declare options** + +**`clan.{moduleName}`** + +???+ Example + The following example shows how to register options in the module interface + + and how it can be set via the inventory + + + ```nix title="/default.nix" + custom-module = ./modules/custom-module; + ``` + + Since the module is called `custom-module` all of its exposed options should be added to `options.clan.custom-module.*...*` + + ```nix title="custom-module/roles/default.nix" + { + options = { + clan.custom-module.foo = mkOption { + type = types.str; + default = "bar"; + }; + }; + } + ``` + + If the module is [registered](#registering-the-module). + Configuration can be set as follows. + + ```nix title="flake.nix" + buildClan { + inventory.services = { + custom-module.instance_1 = { + roles.default.machines = [ "machineA" ]; + roles.default.config = { + # All configuration here is scoped to `clan.custom-module` + foo = "foobar"; + }; + }; + }; + } + ``` + ## Organizing the ClanModule Each `{role}.nix` is included into the machine if the machine is declared to have the role. diff --git a/lib/build-clan/interface.nix b/lib/build-clan/interface.nix index 1ed9b0314..baf1a45fb 100644 --- a/lib/build-clan/interface.nix +++ b/lib/build-clan/interface.nix @@ -101,8 +101,12 @@ in # Those options are interfaced by the CLI # We don't specify the type here, for better performance. inventory = lib.mkOption { type = lib.types.raw; }; + # all inventory module schemas + moduleSchemas = lib.mkOption { type = lib.types.raw; }; inventoryFile = lib.mkOption { type = lib.types.raw; }; + # The machine 'imports' generated by the inventory per machine serviceConfigs = lib.mkOption { type = lib.types.raw; }; + # clan-core's modules clanModules = lib.mkOption { type = lib.types.raw; }; source = lib.mkOption { type = lib.types.raw; }; meta = lib.mkOption { type = lib.types.raw; }; diff --git a/lib/build-clan/module.nix b/lib/build-clan/module.nix index 74deadfb5..a579f9973 100644 --- a/lib/build-clan/module.nix +++ b/lib/build-clan/module.nix @@ -169,6 +169,7 @@ in inherit nixosConfigurations; clanInternals = { + moduleSchemas = clan-core.lib.modules.getModulesSchema config.inventory.modules; inherit serviceConfigs; inherit (clan-core) clanModules; inherit inventoryFile; diff --git a/lib/default.nix b/lib/default.nix index 4b88491cb..e1ab88473 100644 --- a/lib/default.nix +++ b/lib/default.nix @@ -16,5 +16,8 @@ in facts = import ./facts.nix { inherit lib; }; inventory = import ./inventory { inherit lib clan-core; }; jsonschema = import ./jsonschema { inherit lib; }; - modules = import ./frontmatter { inherit lib; }; + modules = import ./frontmatter { + inherit lib; + self = clan-core; + }; } diff --git a/lib/frontmatter/default.nix b/lib/frontmatter/default.nix index e200f0b01..cd8a302f4 100644 --- a/lib/frontmatter/default.nix +++ b/lib/frontmatter/default.nix @@ -1,8 +1,20 @@ -{ lib }: +{ lib, self }: let # Trim the .nix extension from a filename trimExtension = name: builtins.substring 0 (builtins.stringLength name - 4) name; + jsonWithoutHeader = self.lib.jsonschema { + includeDefaults = true; + header = { }; + }; + + getModulesSchema = + modules: + lib.mapAttrs ( + _moduleName: rolesOptions: + lib.mapAttrs (_roleName: options: jsonWithoutHeader.parseOptions options { }) rolesOptions + ) (self.lib.evalClanModulesWithRoles modules); + evalFrontmatter = { moduleName, @@ -90,7 +102,7 @@ in { inherit frontmatterOptions - + getModulesSchema getFrontmatter checkConstraints diff --git a/lib/inventory/schemas/default.nix b/lib/inventory/schemas/default.nix index 56d508918..f6a6660a1 100644 --- a/lib/inventory/schemas/default.nix +++ b/lib/inventory/schemas/default.nix @@ -2,25 +2,14 @@ self, self', pkgs, - lib, ... }: let - includeDefaults = true; - # { mName :: { roleName :: Options } } - modulesRolesOptions = self.lib.evalClanModulesWithRoles self.clanModules; - modulesSchema = lib.mapAttrs ( - _moduleName: rolesOptions: - lib.mapAttrs (_roleName: options: jsonWithoutHeader.parseOptions options { }) rolesOptions - ) modulesRolesOptions; + modulesSchema = self.lib.modules.getModulesSchema self.clanModules; jsonLib = self.lib.jsonschema { inherit includeDefaults; }; - - jsonWithoutHeader = self.lib.jsonschema { - inherit includeDefaults; - header = { }; - }; + includeDefaults = true; frontMatterSchema = jsonLib.parseOptions self.lib.modules.frontmatterOptions { }; diff --git a/pkgs/clan-cli/clan_cli/api/modules.py b/pkgs/clan-cli/clan_cli/api/modules.py index 746afc628..d09da67c5 100644 --- a/pkgs/clan-cli/clan_cli/api/modules.py +++ b/pkgs/clan-cli/clan_cli/api/modules.py @@ -145,7 +145,7 @@ class ModuleInfo: def get_modules(base_path: str) -> dict[str, str]: cmd = nix_eval( [ - f"{base_path}#clanInternals.clanModules", + f"{base_path}#clanInternals.inventory.modules", "--json", ] ) @@ -153,11 +153,11 @@ def get_modules(base_path: str) -> dict[str, str]: proc = run_no_stdout(cmd) res = proc.stdout.strip() except ClanCmdError as e: - msg = "clanInternals might not have clanModules attributes" + msg = "clanInternals might not have inventory.modules attributes" raise ClanError( msg, location=f"list_modules {base_path}", - description="Evaluation failed on clanInternals.clanModules attribute", + description="Evaluation failed on clanInternals.inventory.modules attribute", ) from e modules: dict[str, str] = json.loads(res) return modules