Merge pull request 'inventory modules: expose module schemas at runtime' (#2469) from hsjobeki/clan-core:inventory-modules into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/2469
This commit is contained in:
@@ -41,6 +41,8 @@ clanModules/borgbackup
|
|||||||
|
|
||||||
The `roles` folder is strictly required for `features = [ "inventory" ]`.
|
The `roles` folder is strictly required for `features = [ "inventory" ]`.
|
||||||
|
|
||||||
|
## Registering the module
|
||||||
|
|
||||||
=== "User module"
|
=== "User module"
|
||||||
|
|
||||||
If the module should be ad-hoc loaded.
|
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
|
## Organizing the ClanModule
|
||||||
|
|
||||||
Each `{role}.nix` is included into the machine if the machine is declared to have the role.
|
Each `{role}.nix` is included into the machine if the machine is declared to have the role.
|
||||||
|
|||||||
@@ -101,8 +101,12 @@ in
|
|||||||
# Those options are interfaced by the CLI
|
# Those options are interfaced by the CLI
|
||||||
# We don't specify the type here, for better performance.
|
# We don't specify the type here, for better performance.
|
||||||
inventory = lib.mkOption { type = lib.types.raw; };
|
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; };
|
inventoryFile = lib.mkOption { type = lib.types.raw; };
|
||||||
|
# The machine 'imports' generated by the inventory per machine
|
||||||
serviceConfigs = lib.mkOption { type = lib.types.raw; };
|
serviceConfigs = lib.mkOption { type = lib.types.raw; };
|
||||||
|
# clan-core's modules
|
||||||
clanModules = lib.mkOption { type = lib.types.raw; };
|
clanModules = lib.mkOption { type = lib.types.raw; };
|
||||||
source = lib.mkOption { type = lib.types.raw; };
|
source = lib.mkOption { type = lib.types.raw; };
|
||||||
meta = lib.mkOption { type = lib.types.raw; };
|
meta = lib.mkOption { type = lib.types.raw; };
|
||||||
|
|||||||
@@ -169,6 +169,7 @@ in
|
|||||||
inherit nixosConfigurations;
|
inherit nixosConfigurations;
|
||||||
|
|
||||||
clanInternals = {
|
clanInternals = {
|
||||||
|
moduleSchemas = clan-core.lib.modules.getModulesSchema config.inventory.modules;
|
||||||
inherit serviceConfigs;
|
inherit serviceConfigs;
|
||||||
inherit (clan-core) clanModules;
|
inherit (clan-core) clanModules;
|
||||||
inherit inventoryFile;
|
inherit inventoryFile;
|
||||||
|
|||||||
@@ -16,5 +16,8 @@ in
|
|||||||
facts = import ./facts.nix { inherit lib; };
|
facts = import ./facts.nix { inherit lib; };
|
||||||
inventory = import ./inventory { inherit lib clan-core; };
|
inventory = import ./inventory { inherit lib clan-core; };
|
||||||
jsonschema = import ./jsonschema { inherit lib; };
|
jsonschema = import ./jsonschema { inherit lib; };
|
||||||
modules = import ./frontmatter { inherit lib; };
|
modules = import ./frontmatter {
|
||||||
|
inherit lib;
|
||||||
|
self = clan-core;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,20 @@
|
|||||||
{ lib }:
|
{ lib, self }:
|
||||||
let
|
let
|
||||||
# Trim the .nix extension from a filename
|
# Trim the .nix extension from a filename
|
||||||
trimExtension = name: builtins.substring 0 (builtins.stringLength name - 4) name;
|
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 =
|
evalFrontmatter =
|
||||||
{
|
{
|
||||||
moduleName,
|
moduleName,
|
||||||
@@ -90,7 +102,7 @@ in
|
|||||||
{
|
{
|
||||||
inherit
|
inherit
|
||||||
frontmatterOptions
|
frontmatterOptions
|
||||||
|
getModulesSchema
|
||||||
getFrontmatter
|
getFrontmatter
|
||||||
|
|
||||||
checkConstraints
|
checkConstraints
|
||||||
|
|||||||
@@ -2,25 +2,14 @@
|
|||||||
self,
|
self,
|
||||||
self',
|
self',
|
||||||
pkgs,
|
pkgs,
|
||||||
lib,
|
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
includeDefaults = true;
|
|
||||||
|
|
||||||
# { mName :: { roleName :: Options } }
|
modulesSchema = self.lib.modules.getModulesSchema self.clanModules;
|
||||||
modulesRolesOptions = self.lib.evalClanModulesWithRoles self.clanModules;
|
|
||||||
modulesSchema = lib.mapAttrs (
|
|
||||||
_moduleName: rolesOptions:
|
|
||||||
lib.mapAttrs (_roleName: options: jsonWithoutHeader.parseOptions options { }) rolesOptions
|
|
||||||
) modulesRolesOptions;
|
|
||||||
|
|
||||||
jsonLib = self.lib.jsonschema { inherit includeDefaults; };
|
jsonLib = self.lib.jsonschema { inherit includeDefaults; };
|
||||||
|
includeDefaults = true;
|
||||||
jsonWithoutHeader = self.lib.jsonschema {
|
|
||||||
inherit includeDefaults;
|
|
||||||
header = { };
|
|
||||||
};
|
|
||||||
|
|
||||||
frontMatterSchema = jsonLib.parseOptions self.lib.modules.frontmatterOptions { };
|
frontMatterSchema = jsonLib.parseOptions self.lib.modules.frontmatterOptions { };
|
||||||
|
|
||||||
|
|||||||
@@ -145,7 +145,7 @@ class ModuleInfo:
|
|||||||
def get_modules(base_path: str) -> dict[str, str]:
|
def get_modules(base_path: str) -> dict[str, str]:
|
||||||
cmd = nix_eval(
|
cmd = nix_eval(
|
||||||
[
|
[
|
||||||
f"{base_path}#clanInternals.clanModules",
|
f"{base_path}#clanInternals.inventory.modules",
|
||||||
"--json",
|
"--json",
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
@@ -153,11 +153,11 @@ def get_modules(base_path: str) -> dict[str, str]:
|
|||||||
proc = run_no_stdout(cmd)
|
proc = run_no_stdout(cmd)
|
||||||
res = proc.stdout.strip()
|
res = proc.stdout.strip()
|
||||||
except ClanCmdError as e:
|
except ClanCmdError as e:
|
||||||
msg = "clanInternals might not have clanModules attributes"
|
msg = "clanInternals might not have inventory.modules attributes"
|
||||||
raise ClanError(
|
raise ClanError(
|
||||||
msg,
|
msg,
|
||||||
location=f"list_modules {base_path}",
|
location=f"list_modules {base_path}",
|
||||||
description="Evaluation failed on clanInternals.clanModules attribute",
|
description="Evaluation failed on clanInternals.inventory.modules attribute",
|
||||||
) from e
|
) from e
|
||||||
modules: dict[str, str] = json.loads(res)
|
modules: dict[str, str] = json.loads(res)
|
||||||
return modules
|
return modules
|
||||||
|
|||||||
Reference in New Issue
Block a user