ADR: init clanModules architecture decision
This commit is contained in:
committed by
clan-bot
parent
775088ccd9
commit
283aad7ea0
181
decisions/01-ClanModules.md
Normal file
181
decisions/01-ClanModules.md
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
# Interface for module authors
|
||||||
|
|
||||||
|
Status: proposed
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
ClanModules define cross `machine` `services`.
|
||||||
|
Services can be `instantiated` multiple times.
|
||||||
|
To make configuration more natural each service instance has multiple `roles` (.i.e. `client` `server`)
|
||||||
|
|
||||||
|
Overall this makes it a 4-Dimensional problem where n-`machines` can have m-`services`, m-`services` can have k-`instances` and k-`instances` can have l-`roles`.
|
||||||
|
|
||||||
|
### Problems
|
||||||
|
|
||||||
|
Problems with the current way of writing clanModules.
|
||||||
|
|
||||||
|
1. No way to retrieve the config of a single service instance
|
||||||
|
2. -> We can't really support multiple instances even though the nix module *could* support multiple instances
|
||||||
|
3. the "config" of a service MUST be evaluated against the "interface" outside of any machine.
|
||||||
|
-> Solution: evaluate each instance config and store it in seperate attributes
|
||||||
|
3. Can't merge multiple config instances -> Example: https://pad.lassul.us/i9uSspVbRUmiBmsJ8OR1ug#
|
||||||
|
-> Propsed change: feed the instance config into a helper that defines the merge behavior
|
||||||
|
5. Writing modules for multiple instances is cumbersome
|
||||||
|
-> Propsed change: Helper utility such as "perInstance"
|
||||||
|
6. No clearly separated, role based interface that is usable via json based APIs
|
||||||
|
1. Currently we just import the module into a test machine and try to figure out what changes
|
||||||
|
-> Proposed change: enforce machine agnostic inventory interface definitions
|
||||||
|
|
||||||
|
### Some immediate ideas
|
||||||
|
|
||||||
|
#### simple idea
|
||||||
|
|
||||||
|
Just use another nested attribute set
|
||||||
|
|
||||||
|
- `clan.something.borgbackup.instance1 = config`
|
||||||
|
- `clan.something.borgbackup.instance1 = config`
|
||||||
|
|
||||||
|
Problems solved:
|
||||||
|
|
||||||
|
- each instance has its own config
|
||||||
|
|
||||||
|
Problems not solved:
|
||||||
|
|
||||||
|
- Machine independent seperated interface
|
||||||
|
- Still cumbersome to merge the instances
|
||||||
|
- Unclear how to get to each config because the interface also depends on the role
|
||||||
|
|
||||||
|
#### Improved Idea
|
||||||
|
|
||||||
|
- PerInstance: utility that maps over all instances and defines what needs to be set for each instance and receives the instanceConfig as argument.
|
||||||
|
- GlobalConfig: Some stuff that needs to be set statically on a machine if the service is enabled.
|
||||||
|
|
||||||
|
## Proposed Change
|
||||||
|
|
||||||
|
Change the ClanModule interface
|
||||||
|
|
||||||
|
`client.nix`
|
||||||
|
```nix
|
||||||
|
{
|
||||||
|
# Allows backwards compatibility
|
||||||
|
_class = "clan";
|
||||||
|
|
||||||
|
# Allows importing other service modules
|
||||||
|
module.zerotier = {
|
||||||
|
# function that receives each instance along with its config and resolved roles
|
||||||
|
perInstance =
|
||||||
|
{
|
||||||
|
# serviceConfig is the config of this instance
|
||||||
|
# as described in interface attribute below.
|
||||||
|
# Merged without differing priority:
|
||||||
|
# service.<instanceName>.config
|
||||||
|
# + role.<roleName>.config
|
||||||
|
# + machine.<machineName>.config
|
||||||
|
serviceConfig,
|
||||||
|
# : string
|
||||||
|
instanceName,
|
||||||
|
# machine :: { name :: string , roles :: listOf string; }
|
||||||
|
machine,
|
||||||
|
# : { {roleName} :: { machines :: listOf string; } }
|
||||||
|
roles,
|
||||||
|
... }:
|
||||||
|
{
|
||||||
|
nixosModule = {
|
||||||
|
config.debug."${instanceName}-client" = serviceConfig;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
# Function that is appplied once for this role
|
||||||
|
perService = {
|
||||||
|
nixosModule =
|
||||||
|
{ lib, ... }:
|
||||||
|
{
|
||||||
|
# Some shared code should be put into a shared file
|
||||||
|
# Which is then imported into all/some roles
|
||||||
|
imports = [
|
||||||
|
../shared.nix
|
||||||
|
];
|
||||||
|
options.debug = lib.mkOption {
|
||||||
|
type = lib.types.attrsOf lib.types.raw;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# Describes the settings how a `client` can be configured.
|
||||||
|
# These are the `options` of serviceConfig of a `client`. (Since: This file is called client.nix)
|
||||||
|
interface =
|
||||||
|
{ lib, ... }:
|
||||||
|
{
|
||||||
|
options.foo = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "bar";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Not Changed! (existing inventory.services for api configuration of clanModules)
|
||||||
|
|
||||||
|
```nix
|
||||||
|
{
|
||||||
|
inventory.services = {
|
||||||
|
test."instance1" = {
|
||||||
|
roles.client.tags = [ "all" ];
|
||||||
|
machines.foo.config = { ... };
|
||||||
|
roles.client.config = { ... };
|
||||||
|
|
||||||
|
};
|
||||||
|
test."instance2" = {
|
||||||
|
roles.server.tags = [ "all" ];
|
||||||
|
config = { ... };
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
We don't propse to change anything here. Because we had no problems with that interface yet.
|
||||||
|
We even like it because it is very fast to evaluate and can be configured via UI.
|
||||||
|
|
||||||
|
### Example of borgbackup before and after
|
||||||
|
|
||||||
|
```nix
|
||||||
|
# client.nix (BEFORE)
|
||||||
|
{config, ...}:
|
||||||
|
let
|
||||||
|
instances = config.clan.inventory.services.borgbackup or { };
|
||||||
|
# roles = { ${role_name} :: { machines :: [string] } }
|
||||||
|
allServers = lib.foldlAttrs (
|
||||||
|
acc: _instanceName: instanceConfig:
|
||||||
|
acc
|
||||||
|
++ (
|
||||||
|
if builtins.elem machineName instanceConfig.roles.client.machines then
|
||||||
|
instanceConfig.roles.server.machines
|
||||||
|
else
|
||||||
|
[ ]
|
||||||
|
)
|
||||||
|
) [ ] instances;
|
||||||
|
|
||||||
|
machineName = config.clan.core.settings.machine.name;
|
||||||
|
|
||||||
|
cfg = config.clan.borgbackup;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
# ... Some nixos config
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```nix
|
||||||
|
# client.nix (AFTER)
|
||||||
|
{
|
||||||
|
_class = "clan";
|
||||||
|
perInstance = {instanceName, serviceConfig, machine, roles }: {
|
||||||
|
# allServers => roles.server;
|
||||||
|
# machineName => machine.name;
|
||||||
|
# cfg => serviceConfig;
|
||||||
|
nixosModule = {config, ...}: {
|
||||||
|
# ...some nixos config
|
||||||
|
}
|
||||||
|
# Extendable. I.e. Instances can also define 'vars' or other things later.
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
Reference in New Issue
Block a user