Merge pull request 'docs(service-modules): add description and docs for options' (#3848) from doc-1 into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/3848
This commit is contained in:
@@ -80,7 +80,7 @@ nav:
|
|||||||
- macOS: guides/macos.md
|
- macOS: guides/macos.md
|
||||||
- Reference:
|
- Reference:
|
||||||
- Overview: reference/index.md
|
- Overview: reference/index.md
|
||||||
- Clan Services:
|
- Services:
|
||||||
- Overview: reference/clanServices/index.md
|
- Overview: reference/clanServices/index.md
|
||||||
- reference/clanServices/admin.md
|
- reference/clanServices/admin.md
|
||||||
- reference/clanServices/auto-upgrade.md
|
- reference/clanServices/auto-upgrade.md
|
||||||
@@ -93,7 +93,8 @@ nav:
|
|||||||
- reference/clanServices/hello-world.md
|
- reference/clanServices/hello-world.md
|
||||||
- reference/clanServices/wifi.md
|
- reference/clanServices/wifi.md
|
||||||
- reference/clanServices/zerotier.md
|
- reference/clanServices/zerotier.md
|
||||||
- Clan Modules:
|
- Interface for making Services: reference/clanServices/clan-service-author-interface.md
|
||||||
|
- Modules:
|
||||||
- Overview: reference/clanModules/index.md
|
- Overview: reference/clanModules/index.md
|
||||||
- reference/clanModules/frontmatter/index.md
|
- reference/clanModules/frontmatter/index.md
|
||||||
# TODO: display the docs of the clan.service modules
|
# TODO: display the docs of the clan.service modules
|
||||||
|
|||||||
@@ -90,6 +90,7 @@
|
|||||||
export CLAN_MODULES_VIA_ROLES=${clanModulesViaRoles}
|
export CLAN_MODULES_VIA_ROLES=${clanModulesViaRoles}
|
||||||
export CLAN_MODULES_VIA_SERVICE=${clanModulesViaService}
|
export CLAN_MODULES_VIA_SERVICE=${clanModulesViaService}
|
||||||
export CLAN_MODULES_VIA_NIX=${clanModulesViaNix}
|
export CLAN_MODULES_VIA_NIX=${clanModulesViaNix}
|
||||||
|
export CLAN_SERVICE_INTERFACE=${self'.legacyPackages.clan-service-module-interface}/share/doc/nixos/options.json
|
||||||
# Frontmatter format for clanModules
|
# Frontmatter format for clanModules
|
||||||
export CLAN_MODULES_FRONTMATTER_DOCS=${clanModulesFrontmatter}/share/doc/nixos/options.json
|
export CLAN_MODULES_FRONTMATTER_DOCS=${clanModulesFrontmatter}/share/doc/nixos/options.json
|
||||||
|
|
||||||
|
|||||||
@@ -49,6 +49,10 @@ CLAN_MODULES_VIA_NIX = os.environ.get("CLAN_MODULES_VIA_NIX")
|
|||||||
# Some modules can be imported via inventory
|
# Some modules can be imported via inventory
|
||||||
CLAN_MODULES_VIA_ROLES = os.environ.get("CLAN_MODULES_VIA_ROLES")
|
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")
|
||||||
|
|
||||||
CLAN_MODULES_VIA_SERVICE = os.environ.get("CLAN_MODULES_VIA_SERVICE")
|
CLAN_MODULES_VIA_SERVICE = os.environ.get("CLAN_MODULES_VIA_SERVICE")
|
||||||
|
|
||||||
OUT = os.environ.get("out")
|
OUT = os.environ.get("out")
|
||||||
@@ -309,7 +313,7 @@ def produce_clan_core_docs() -> None:
|
|||||||
core_outputs[indexfile] += """!!! info "Submodules"\n"""
|
core_outputs[indexfile] += """!!! info "Submodules"\n"""
|
||||||
|
|
||||||
for submodule_name, split_options in split.items():
|
for submodule_name, split_options in split.items():
|
||||||
root = options_to_tree(split_options, debug=True)
|
root = options_to_tree(split_options)
|
||||||
module = root.suboptions[0]
|
module = root.suboptions[0]
|
||||||
module_type = module.info.get("type")
|
module_type = module.info.get("type")
|
||||||
if module_type is not None and "submodule" not in module_type:
|
if module_type is not None and "submodule" not in module_type:
|
||||||
@@ -798,6 +802,44 @@ def split_options_by_root(options: dict[str, Any]) -> dict[str, dict[str, Any]]:
|
|||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
def produce_clan_service_author_docs() -> None:
|
||||||
|
if not CLAN_SERVICE_INTERFACE:
|
||||||
|
msg = f"Environment variables are not set correctly: CLAN_SERVICE_INTERFACE={CLAN_SERVICE_INTERFACE}. Expected a path to the optionsJSON"
|
||||||
|
raise ClanError(msg)
|
||||||
|
|
||||||
|
if not OUT:
|
||||||
|
msg = f"Environment variables are not set correctly: $out={OUT}"
|
||||||
|
raise ClanError(msg)
|
||||||
|
|
||||||
|
output = """
|
||||||
|
This document describes the structure and configurable attributes of a `clan.service` module.
|
||||||
|
|
||||||
|
Typically needed by module authors to define roles, behavior and metadata for distributed services.
|
||||||
|
|
||||||
|
!!! Note
|
||||||
|
This is not a user-facing documentation, but rather meant as a reference for *module authors*
|
||||||
|
|
||||||
|
See: [clanService Authoring Guide](../../guides/authoring/clanServices/index.md)
|
||||||
|
"""
|
||||||
|
# Inventory options are already included under the buildClan attribute
|
||||||
|
# We just omitted them in the buildClan docs, because we want a separate output for the inventory model
|
||||||
|
with Path(CLAN_SERVICE_INTERFACE).open() as f:
|
||||||
|
options: dict[str, dict[str, Any]] = json.load(f)
|
||||||
|
|
||||||
|
options_tree = options_to_tree(options, debug=True)
|
||||||
|
# Find the inventory options
|
||||||
|
|
||||||
|
# Render the inventory options
|
||||||
|
# This for loop excludes the root node
|
||||||
|
# for option in options_tree.suboptions:
|
||||||
|
output += options_docs_from_tree(options_tree, init_level=2)
|
||||||
|
|
||||||
|
outfile = Path(OUT) / "clanServices/clan-service-author-interface.md"
|
||||||
|
outfile.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
with Path.open(outfile, "w") as of:
|
||||||
|
of.write(output)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Option:
|
class Option:
|
||||||
name: str
|
name: str
|
||||||
@@ -959,6 +1001,8 @@ if __name__ == "__main__": #
|
|||||||
produce_build_clan_docs()
|
produce_build_clan_docs()
|
||||||
produce_inventory_docs()
|
produce_inventory_docs()
|
||||||
|
|
||||||
|
produce_clan_service_author_docs()
|
||||||
|
|
||||||
produce_clan_modules_docs()
|
produce_clan_modules_docs()
|
||||||
produce_clan_service_docs()
|
produce_clan_service_docs()
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,9 @@
|
|||||||
## Service Module Specification
|
## Service Module Specification
|
||||||
|
|
||||||
This section explains how to author a clan service module.
|
This section explains how to author a clan service module.
|
||||||
We discussed the initial architecture in [01-clan-service-modules](../../../decisions/01-ClanModules.md) and decided to rework the format as follows:
|
We discussed the initial architecture in [01-clan-service-modules](../../../decisions/01-ClanModules.md) and decided to rework the format.
|
||||||
|
|
||||||
|
For the full specification and current state see: **[Service Author Reference](../../../reference/clanServices/clan-service-author-interface.md)**
|
||||||
|
|
||||||
### A Minimal module
|
### A Minimal module
|
||||||
|
|
||||||
@@ -49,6 +51,8 @@ The imported module file must fulfill at least the following requirements:
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
For more attributes see: **[Service Author Reference](../../../reference/clanServices/clan-service-author-interface.md)**
|
||||||
|
|
||||||
### Adding functionality to the module
|
### Adding functionality to the module
|
||||||
|
|
||||||
While the very minimal module is valid in itself it has no way of adding any machines to it, because it doesn't specify any roles.
|
While the very minimal module is valid in itself it has no way of adding any machines to it, because it doesn't specify any roles.
|
||||||
@@ -254,3 +258,11 @@ outputs = inputs: flake-parts.lib.mkFlake { inherit inputs; } ({self, lib, ...}:
|
|||||||
```
|
```
|
||||||
|
|
||||||
The benefit of this approach is that downstream users can override the value of `myClan` by using `mkForce` or other priority modifiers.
|
The benefit of this approach is that downstream users can override the value of `myClan` by using `mkForce` or other priority modifiers.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Further
|
||||||
|
|
||||||
|
- [Reference Documentation for Service Authors](../../../reference/clanServices/clan-service-author-interface.md)
|
||||||
|
- [Migration Guide from ClanModules to ClanServices](../../migrations/migrate-inventory-services.md)
|
||||||
|
- [Decision that lead to ClanServices](../../../decisions/01-ClanModules.md)
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ in
|
|||||||
{ lib, config, ... }:
|
{ lib, config, ... }:
|
||||||
{
|
{
|
||||||
options.result.api = lib.mkOption {
|
options.result.api = lib.mkOption {
|
||||||
|
visible = false;
|
||||||
default = { };
|
default = { };
|
||||||
type = lib.types.submodule ({
|
type = lib.types.submodule ({
|
||||||
options.schema = lib.mkOption {
|
options.schema = lib.mkOption {
|
||||||
|
|||||||
@@ -26,13 +26,6 @@ let
|
|||||||
${builtins.toJSON (lib.attrNames config.roles)}
|
${builtins.toJSON (lib.attrNames config.roles)}
|
||||||
'';
|
'';
|
||||||
|
|
||||||
# checkInstanceSettings =
|
|
||||||
# instanceName: instanceSettings:
|
|
||||||
# let
|
|
||||||
# unmatchedRoles = 1;
|
|
||||||
# in
|
|
||||||
# unmatchedRoles;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Merges the role- and machine-settings using the role interface
|
Merges the role- and machine-settings using the role interface
|
||||||
|
|
||||||
@@ -154,11 +147,29 @@ let
|
|||||||
in
|
in
|
||||||
{
|
{
|
||||||
options = {
|
options = {
|
||||||
|
# TODO: deduplicate this with inventory.instances
|
||||||
|
# Although inventory has stricter constraints
|
||||||
instances = mkOption {
|
instances = mkOption {
|
||||||
|
# Instances are created in the inventory
|
||||||
|
visible = false;
|
||||||
|
defaultText = "Throws: 'The service must define its instances' when not defined";
|
||||||
default = throw ''
|
default = throw ''
|
||||||
The clan service module ${config.manifest.name} doesn't define any instances.
|
The clan service module ${config.manifest.name} doesn't define any instances.
|
||||||
|
|
||||||
Did you forget to create instances via 'inventory.instances' ?
|
Did you forget to create instances via 'inventory.instances'?
|
||||||
|
'';
|
||||||
|
description = ''
|
||||||
|
Instances of the service.
|
||||||
|
|
||||||
|
An Instance is a user-specific deployment or configuration of a service.
|
||||||
|
It represents the active usage of the service configured to the user's settings or use case.
|
||||||
|
The `<instanceName>` of the instance is arbitrary, but must be unique.
|
||||||
|
|
||||||
|
A common best practice is to name the instance after the 'service' and the 'use-case'.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
- 'instances.zerotier-homelab = ...' for a zerotier instance that connects all machines of a homelab
|
||||||
'';
|
'';
|
||||||
|
|
||||||
type = attrsWith {
|
type = attrsWith {
|
||||||
@@ -174,6 +185,21 @@ in
|
|||||||
# apply = v: lib.seq (checkInstanceSettings name v) v;
|
# apply = v: lib.seq (checkInstanceSettings name v) v;
|
||||||
# };
|
# };
|
||||||
options.roles = mkOption {
|
options.roles = mkOption {
|
||||||
|
description = ''
|
||||||
|
Roles of the instance.
|
||||||
|
|
||||||
|
A role is a specific behavior or configuration of the service.
|
||||||
|
It defines how the service should behave in the context of this instance.
|
||||||
|
The `<roleName>` must match one of the roles defined in the service
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
- 'roles.client = ...' for a client role that connects to the service
|
||||||
|
- 'roles.server = ...' for a server role that provides the service
|
||||||
|
|
||||||
|
Throws an error if empty, since this would mean that the service has no members.
|
||||||
|
'';
|
||||||
|
defaultText = "Throws: 'The service must define members via roles' when not defined";
|
||||||
default = throw ''
|
default = throw ''
|
||||||
Instance '${name}' of service '${config.manifest.name}' mut define members via 'roles'.
|
Instance '${name}' of service '${config.manifest.name}' mut define members via 'roles'.
|
||||||
|
|
||||||
@@ -184,43 +210,51 @@ in
|
|||||||
placeholder = "roleName";
|
placeholder = "roleName";
|
||||||
elemType = submoduleWith {
|
elemType = submoduleWith {
|
||||||
modules = [
|
modules = [
|
||||||
(
|
({
|
||||||
{ ... }:
|
# instances.{instanceName}.roles.{roleName}.machines
|
||||||
{
|
options.machines = mkOption {
|
||||||
# instances.{instanceName}.roles.{roleName}.machines
|
description = ''
|
||||||
options.machines = mkOption {
|
Machines of the role.
|
||||||
type = attrsWith {
|
|
||||||
placeholder = "machineName";
|
A machine is a physical or virtual machine that is part of the instance.
|
||||||
elemType = submoduleWith {
|
The `<machineName>` must match the name of any machine defined in the clan.
|
||||||
modules = [
|
|
||||||
(m: {
|
For example:
|
||||||
options.settings = mkOption {
|
|
||||||
type = types.raw;
|
- 'machines.my-machine = { ...; }' for a machine that is part of the instance
|
||||||
description = "Settings of '${name}-machine': ${m.name}.";
|
- 'machines.my-other-machine = { ...; }' for another machine that is part of the instance
|
||||||
default = { };
|
'';
|
||||||
};
|
type = attrsWith {
|
||||||
})
|
placeholder = "machineName";
|
||||||
];
|
elemType = submoduleWith {
|
||||||
};
|
modules = [
|
||||||
|
(m: {
|
||||||
|
options.settings = mkOption {
|
||||||
|
type = types.raw;
|
||||||
|
description = "Settings of '${name}-machine': ${m.name or "<machineName>"}.";
|
||||||
|
default = { };
|
||||||
|
};
|
||||||
|
})
|
||||||
|
];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
};
|
||||||
|
|
||||||
# instances.{instanceName}.roles.{roleName}.settings
|
# instances.{instanceName}.roles.{roleName}.settings
|
||||||
# options._settings = mkOption { };
|
# options._settings = mkOption { };
|
||||||
# options._settingsViaTags = mkOption { };
|
# options._settingsViaTags = mkOption { };
|
||||||
# A deferred module that combines _settingsViaTags with _settings
|
# A deferred module that combines _settingsViaTags with _settings
|
||||||
options.settings = mkOption {
|
options.settings = mkOption {
|
||||||
type = types.raw;
|
type = types.raw;
|
||||||
description = "Settings of 'role': ${name}";
|
description = "Settings of 'role': ${name}";
|
||||||
default = { };
|
default = { };
|
||||||
};
|
};
|
||||||
|
|
||||||
options.extraModules = lib.mkOption {
|
options.extraModules = lib.mkOption {
|
||||||
default = [ ];
|
default = [ ];
|
||||||
type = types.listOf (types.deferredModule);
|
type = types.listOf (types.deferredModule);
|
||||||
};
|
};
|
||||||
}
|
})
|
||||||
)
|
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -242,6 +276,22 @@ in
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
roles = mkOption {
|
roles = mkOption {
|
||||||
|
description = ''
|
||||||
|
Roles of the service.
|
||||||
|
|
||||||
|
A role is a specific behavior or configuration of the service.
|
||||||
|
It defines how the service should behave in the context of the clan.
|
||||||
|
|
||||||
|
The `<roleName>`s of the service are defined here. Later usage of the roles must match one of the `roleNames`.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
- 'roles.client = ...' for a client role that connects to the service
|
||||||
|
- 'roles.server = ...' for a server role that provides the service
|
||||||
|
|
||||||
|
Throws an error if empty, since this would mean that the service has no way of adding members.
|
||||||
|
'';
|
||||||
|
defaultText = "Throws: 'The service must define its roles' when not defined";
|
||||||
default = throw ''
|
default = throw ''
|
||||||
Role behavior of service '${config.manifest.name}' must be defined.
|
Role behavior of service '${config.manifest.name}' must be defined.
|
||||||
A 'clan.service' module should always define its behavior via 'roles'
|
A 'clan.service' module should always define its behavior via 'roles'
|
||||||
@@ -263,32 +313,138 @@ in
|
|||||||
in
|
in
|
||||||
{
|
{
|
||||||
options.interface = mkOption {
|
options.interface = mkOption {
|
||||||
|
description = ''
|
||||||
|
Abstract interface of the role.
|
||||||
|
|
||||||
|
This is an abstract module which should define 'options' for the role's settings.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
{
|
||||||
|
options.timeout = mkOption {
|
||||||
|
type = types.int;
|
||||||
|
default = 30;
|
||||||
|
description = "Timeout in seconds";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Note:
|
||||||
|
|
||||||
|
- `machine.config` is not available here, since the role is definition is abstract.
|
||||||
|
- *defaults* that depend on the *machine* or *instance* should be added to *settings* later in 'perInstance' or 'perMachine'
|
||||||
|
'';
|
||||||
type = types.deferredModule;
|
type = types.deferredModule;
|
||||||
# TODO: Default to an empty module
|
# TODO: Default to an empty module
|
||||||
# need to test that an the empty module can be evaluated to empty settings
|
# need to test that an the empty module can be evaluated to empty settings
|
||||||
default = { };
|
default = { };
|
||||||
};
|
};
|
||||||
options.perInstance = mkOption {
|
options.perInstance = mkOption {
|
||||||
type = types.deferredModuleWith {
|
description = ''
|
||||||
staticModules = [
|
Per-instance configuration of the role.
|
||||||
# Common output format
|
|
||||||
# As described by adr
|
This option is used to define instance-specific behavior for the service-role. (Example below)
|
||||||
# { nixosModule, services, ... }
|
|
||||||
(
|
Although the type is a `deferredModule`, it helps to think of it as a function.
|
||||||
{ ... }:
|
The 'function' takes the `instance-name` and some other `arguments`.
|
||||||
|
|
||||||
|
*Arguments*:
|
||||||
|
|
||||||
|
- `instanceName` (`string`): The name of the instance.
|
||||||
|
- `machine`: Machine information, containing:
|
||||||
|
```nix
|
||||||
{
|
{
|
||||||
options.nixosModule = mkOption { default = { }; };
|
name = "machineName";
|
||||||
options.services = mkOption {
|
roles = ["client" "server" ... ];
|
||||||
type = attrsWith {
|
}
|
||||||
placeholder = "serviceName";
|
```
|
||||||
elemType = submoduleWith {
|
- `roles`: Attribute set of all roles of the instance, in the form:
|
||||||
modules = [ ./service-module.nix ];
|
```nix
|
||||||
|
roles = {
|
||||||
|
client = {
|
||||||
|
machines = {
|
||||||
|
jon = {
|
||||||
|
settings = {
|
||||||
|
timeout = 60;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
default = { };
|
# ...
|
||||||
};
|
};
|
||||||
}
|
settings = {
|
||||||
)
|
timeout = 30;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
# ...
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
- `settings`: The settings of the role, as defined in `inventory`
|
||||||
|
```nix
|
||||||
|
{
|
||||||
|
timeout = 30;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- `extendSettings`: A function that takes a module and returns a new module with extended settings.
|
||||||
|
```nix
|
||||||
|
extendSettings {
|
||||||
|
timeout = mkForce 60;
|
||||||
|
};
|
||||||
|
->
|
||||||
|
{
|
||||||
|
timeout = 60;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
*Returns* an `attribute set` containing:
|
||||||
|
|
||||||
|
- `nixosModule`: The NixOS module for the instance.
|
||||||
|
|
||||||
|
'';
|
||||||
|
type = types.deferredModuleWith {
|
||||||
|
staticModules = [
|
||||||
|
({
|
||||||
|
options.nixosModule = mkOption {
|
||||||
|
type = types.deferredModule;
|
||||||
|
default = { };
|
||||||
|
description = ''
|
||||||
|
This module is later imported to configure the machine with the config derived from service's settings.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
roles.client.perInstance = { instanceName, ... }:
|
||||||
|
{
|
||||||
|
# Keep in mind that this module is produced once per-instance
|
||||||
|
# Meaning you might end up with multiple of these modules.
|
||||||
|
# Make sure they can be imported all together without conflicts
|
||||||
|
#
|
||||||
|
# ↓ nixos-config
|
||||||
|
nixosModule = { config ,... }: {
|
||||||
|
# create one systemd service per instance
|
||||||
|
# It is a common practice to concatenate the *service-name* and *instance-name*
|
||||||
|
# To ensure globally unique systemd-units for the target machine
|
||||||
|
systemd.services."webly-''${instanceName}" = {
|
||||||
|
...
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
# TODO: Recursive services
|
||||||
|
options.services = mkOption {
|
||||||
|
visible = false;
|
||||||
|
type = attrsWith {
|
||||||
|
placeholder = "serviceName";
|
||||||
|
elemType = submoduleWith {
|
||||||
|
modules = [ ./service-module.nix ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
apply = _: throw "Not implemented yet";
|
||||||
|
default = { };
|
||||||
|
};
|
||||||
|
})
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
default = { };
|
default = { };
|
||||||
@@ -333,26 +489,78 @@ in
|
|||||||
};
|
};
|
||||||
|
|
||||||
perMachine = mkOption {
|
perMachine = mkOption {
|
||||||
|
description = ''
|
||||||
|
Per-machine configuration of the service.
|
||||||
|
|
||||||
|
This option is used to define machine-specific settings for the service **once**, if any service-instance is used.
|
||||||
|
|
||||||
|
Although the type is a `deferredModule`, it helps to think of it as a function.
|
||||||
|
The 'function' takes the `machine-name` and some other 'arguments'
|
||||||
|
|
||||||
|
*Arguments*:
|
||||||
|
|
||||||
|
- `machine`: `{ name :: string; roles :: listOf String }`
|
||||||
|
- `instances`: The scope of the machine, containing all instances and roles that the machine is part of.
|
||||||
|
```nix
|
||||||
|
{
|
||||||
|
instances = {
|
||||||
|
<instanceName> = {
|
||||||
|
roles = {
|
||||||
|
<roleName> = {
|
||||||
|
# Per-machine settings
|
||||||
|
machines = { <machineName> = { settings = { ... }; }; }; };
|
||||||
|
# Per-role settings
|
||||||
|
settings = { ... };
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
*Returns* an `attribute set` containing:
|
||||||
|
|
||||||
|
- `nixosModule`: The NixOS module for the machine.
|
||||||
|
|
||||||
|
'';
|
||||||
type = types.deferredModuleWith {
|
type = types.deferredModuleWith {
|
||||||
staticModules = [
|
staticModules = [
|
||||||
# Common output format
|
({
|
||||||
# As described by adr
|
options.nixosModule = mkOption {
|
||||||
# { nixosModule, services, ... }
|
type = types.deferredModule;
|
||||||
(
|
default = { };
|
||||||
{ ... }:
|
description = ''
|
||||||
{
|
A single NixOS module for the machine.
|
||||||
options.nixosModule = mkOption { default = { }; };
|
|
||||||
options.services = mkOption {
|
This module is later imported to configure the machine with the config derived from service's settings.
|
||||||
type = attrsWith {
|
|
||||||
placeholder = "serviceName";
|
Example:
|
||||||
elemType = submoduleWith {
|
|
||||||
modules = [ ./service-module.nix ];
|
```nix
|
||||||
};
|
# ↓ machine.roles ...
|
||||||
|
perMachine = { machine, ... }:
|
||||||
|
{ # ↓ nixos-config
|
||||||
|
nixosModule = { config ,... }: {
|
||||||
|
systemd.services.foo = {
|
||||||
|
enable = true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
# TODO: Recursive services
|
||||||
|
options.services = mkOption {
|
||||||
|
visible = false;
|
||||||
|
type = attrsWith {
|
||||||
|
placeholder = "serviceName";
|
||||||
|
elemType = submoduleWith {
|
||||||
|
modules = [ ./service-module.nix ];
|
||||||
};
|
};
|
||||||
default = { };
|
|
||||||
};
|
};
|
||||||
}
|
apply = _: throw "Not implemented yet";
|
||||||
)
|
default = { };
|
||||||
|
};
|
||||||
|
})
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
default = { };
|
default = { };
|
||||||
@@ -428,6 +636,7 @@ in
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
result.allRoles = mkOption {
|
result.allRoles = mkOption {
|
||||||
|
visible = false;
|
||||||
readOnly = true;
|
readOnly = true;
|
||||||
default = lib.mapAttrs (roleName: roleCfg: {
|
default = lib.mapAttrs (roleName: roleCfg: {
|
||||||
allInstances = lib.mapAttrs (instanceName: instanceCfg: {
|
allInstances = lib.mapAttrs (instanceName: instanceCfg: {
|
||||||
@@ -436,15 +645,14 @@ in
|
|||||||
let
|
let
|
||||||
instanceRes = roleCfg.perInstance instanceName machineName;
|
instanceRes = roleCfg.perInstance instanceName machineName;
|
||||||
in
|
in
|
||||||
{
|
instanceRes
|
||||||
|
// {
|
||||||
nixosModule = {
|
nixosModule = {
|
||||||
imports = [
|
imports = [
|
||||||
# Result of the applied 'perInstance = {...}: { nixosModule = { ... }; }'
|
# Result of the applied 'perInstance = {...}: { nixosModule = { ... }; }'
|
||||||
instanceRes.nixosModule
|
instanceRes.nixosModule
|
||||||
] ++ instanceCfg.roles.${roleName}.extraModules;
|
] ++ instanceCfg.roles.${roleName}.extraModules;
|
||||||
};
|
};
|
||||||
# TODO: nested services
|
|
||||||
services = { };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
) instanceCfg.roles.${roleName}.machines or { };
|
) instanceCfg.roles.${roleName}.machines or { };
|
||||||
@@ -454,10 +662,12 @@ in
|
|||||||
|
|
||||||
result.assertions = mkOption {
|
result.assertions = mkOption {
|
||||||
default = { };
|
default = { };
|
||||||
|
visible = false;
|
||||||
type = types.attrsOf types.raw;
|
type = types.attrsOf types.raw;
|
||||||
};
|
};
|
||||||
|
|
||||||
result.allMachines = mkOption {
|
result.allMachines = mkOption {
|
||||||
|
visible = false;
|
||||||
readOnly = true;
|
readOnly = true;
|
||||||
default =
|
default =
|
||||||
let
|
let
|
||||||
@@ -505,6 +715,7 @@ in
|
|||||||
};
|
};
|
||||||
|
|
||||||
result.final = mkOption {
|
result.final = mkOption {
|
||||||
|
visible = false;
|
||||||
readOnly = true;
|
readOnly = true;
|
||||||
default = lib.mapAttrs (
|
default = lib.mapAttrs (
|
||||||
machineName: machineResult:
|
machineName: machineResult:
|
||||||
|
|||||||
@@ -37,17 +37,19 @@ let
|
|||||||
};
|
};
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
nixosModule = {
|
options.passthru = lib.mkOption {
|
||||||
inherit
|
default = {
|
||||||
instanceName
|
inherit
|
||||||
settings
|
instanceName
|
||||||
machine
|
settings
|
||||||
roles
|
machine
|
||||||
;
|
roles
|
||||||
|
;
|
||||||
|
|
||||||
# We are double vendoring the settings
|
# We are double vendoring the settings
|
||||||
# To test that we can do it indefinitely
|
# To test that we can do it indefinitely
|
||||||
vendoredSettings = finalSettings;
|
vendoredSettings = finalSettings;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -96,30 +98,26 @@ let
|
|||||||
. "foo-peer"; }; }; }; settings = { timeout = "foo-peer-jon"; }; vendoredSettings = { timeout = "conf .
|
. "foo-peer"; }; }; }; settings = { timeout = "foo-peer-jon"; }; vendoredSettings = { timeout = "conf .
|
||||||
. ig.thing"; }; } ]; } .
|
. ig.thing"; }; } ]; } .
|
||||||
*/
|
*/
|
||||||
unwrapModule = m: (builtins.head m.imports);
|
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
# settings should evaluate
|
# settings should evaluate
|
||||||
test_per_instance_arguments = {
|
test_per_instance_arguments = {
|
||||||
expr =
|
expr = {
|
||||||
let
|
instanceName =
|
||||||
m = (
|
res.importedModulesEvaluated.self-A.config.result.allRoles.peer.allInstances."instance_foo".allMachines.jon.passthru.instanceName;
|
||||||
unwrapModule
|
|
||||||
res.importedModulesEvaluated.self-A.config.result.allRoles.peer.allInstances."instance_foo".allMachines.jon.nixosModule
|
|
||||||
);
|
|
||||||
in
|
|
||||||
{
|
|
||||||
instanceName = m.instanceName;
|
|
||||||
|
|
||||||
# settings are specific.
|
# settings are specific.
|
||||||
# Below we access:
|
# Below we access:
|
||||||
# instance = instance_foo
|
# instance = instance_foo
|
||||||
# roles = peer
|
# roles = peer
|
||||||
# machines = jon
|
# machines = jon
|
||||||
settings = m.settings;
|
settings =
|
||||||
machine = m.machine;
|
res.importedModulesEvaluated.self-A.config.result.allRoles.peer.allInstances.instance_foo.allMachines.jon.passthru.settings;
|
||||||
roles = m.roles;
|
machine =
|
||||||
};
|
res.importedModulesEvaluated.self-A.config.result.allRoles.peer.allInstances.instance_foo.allMachines.jon.passthru.machine;
|
||||||
|
roles =
|
||||||
|
res.importedModulesEvaluated.self-A.config.result.allRoles.peer.allInstances.instance_foo.allMachines.jon.passthru.roles;
|
||||||
|
};
|
||||||
expected = {
|
expected = {
|
||||||
instanceName = "instance_foo";
|
instanceName = "instance_foo";
|
||||||
settings = {
|
settings = {
|
||||||
@@ -161,10 +159,9 @@ in
|
|||||||
|
|
||||||
# TODO: Cannot be tested like this anymore
|
# TODO: Cannot be tested like this anymore
|
||||||
test_per_instance_settings_vendoring = {
|
test_per_instance_settings_vendoring = {
|
||||||
|
x = res.importedModulesEvaluated.self-A.config;
|
||||||
expr =
|
expr =
|
||||||
(unwrapModule
|
res.importedModulesEvaluated.self-A.config.result.allRoles.peer.allInstances.instance_foo.allMachines.jon.passthru.vendoredSettings;
|
||||||
res.importedModulesEvaluated.self-A.config.result.allRoles.peer.allInstances."instance_foo".allMachines.jon.nixosModule
|
|
||||||
).vendoredSettings;
|
|
||||||
expected = {
|
expected = {
|
||||||
timeout = "config.thing";
|
timeout = "config.thing";
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -28,8 +28,10 @@ let
|
|||||||
perMachine =
|
perMachine =
|
||||||
{ instances, machine, ... }:
|
{ instances, machine, ... }:
|
||||||
{
|
{
|
||||||
nixosModule = {
|
options.passthru = lib.mkOption {
|
||||||
inherit instances machine;
|
default = {
|
||||||
|
inherit instances machine;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -76,7 +78,7 @@ in
|
|||||||
inherit res;
|
inherit res;
|
||||||
expr = {
|
expr = {
|
||||||
hasMachineSettings =
|
hasMachineSettings =
|
||||||
res.importedModulesEvaluated.self-A.config.result.allMachines.jon.nixosModule.instances.instance_foo.roles.peer.machines.jon
|
res.importedModulesEvaluated.self-A.config.result.allMachines.jon.passthru.instances.instance_foo.roles.peer.machines.jon
|
||||||
? settings;
|
? settings;
|
||||||
|
|
||||||
# settings are specific.
|
# settings are specific.
|
||||||
@@ -84,10 +86,10 @@ in
|
|||||||
# instance = instance_foo
|
# instance = instance_foo
|
||||||
# roles = peer
|
# roles = peer
|
||||||
# machines = jon
|
# machines = jon
|
||||||
specificMachineSettings = filterInternals res.importedModulesEvaluated.self-A.config.result.allMachines.jon.nixosModule.instances.instance_foo.roles.peer.machines.jon.settings;
|
specificMachineSettings = filterInternals res.importedModulesEvaluated.self-A.config.result.allMachines.jon.passthru.instances.instance_foo.roles.peer.machines.jon.settings;
|
||||||
|
|
||||||
hasRoleSettings =
|
hasRoleSettings =
|
||||||
res.importedModulesEvaluated.self-A.config.result.allMachines.jon.nixosModule.instances.instance_foo.roles.peer
|
res.importedModulesEvaluated.self-A.config.result.allMachines.jon.passthru.instances.instance_foo.roles.peer
|
||||||
? settings;
|
? settings;
|
||||||
|
|
||||||
# settings are specific.
|
# settings are specific.
|
||||||
@@ -95,7 +97,7 @@ in
|
|||||||
# instance = instance_foo
|
# instance = instance_foo
|
||||||
# roles = peer
|
# roles = peer
|
||||||
# machines = *
|
# machines = *
|
||||||
specificRoleSettings = filterInternals res.importedModulesEvaluated.self-A.config.result.allMachines.jon.nixosModule.instances.instance_foo.roles.peer.settings;
|
specificRoleSettings = filterInternals res.importedModulesEvaluated.self-A.config.result.allMachines.jon.passthru.instances.instance_foo.roles.peer.settings;
|
||||||
};
|
};
|
||||||
expected = {
|
expected = {
|
||||||
hasMachineSettings = true;
|
hasMachineSettings = true;
|
||||||
|
|||||||
@@ -43,6 +43,16 @@ in
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
legacyPackages.clan-service-module-interface =
|
||||||
|
(pkgs.nixosOptionsDoc {
|
||||||
|
options =
|
||||||
|
(self.clanLib.inventory.evalClanService {
|
||||||
|
modules = [ ];
|
||||||
|
prefix = [ ];
|
||||||
|
}).options;
|
||||||
|
warningsAreErrors = true;
|
||||||
|
}).optionsJSON;
|
||||||
|
|
||||||
# Run: nix-unit --extra-experimental-features flakes --flake .#legacyPackages.x86_64-linux.evalTests
|
# Run: nix-unit --extra-experimental-features flakes --flake .#legacyPackages.x86_64-linux.evalTests
|
||||||
legacyPackages.evalTests-inventory = import ./tests {
|
legacyPackages.evalTests-inventory = import ./tests {
|
||||||
inherit lib;
|
inherit lib;
|
||||||
|
|||||||
@@ -20,6 +20,5 @@
|
|||||||
in
|
in
|
||||||
{
|
{
|
||||||
imports = [ test-types-module ];
|
imports = [ test-types-module ];
|
||||||
legacyPackages.xxx = { };
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user