Merge pull request 'feat(inventory/instances): add option for extraModules to roles' (#3830) from flake-models into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/3830
This commit is contained in:
@@ -30,16 +30,13 @@ let
|
|||||||
# config.distributedServices.allMachines.${name} or [ ];
|
# config.distributedServices.allMachines.${name} or [ ];
|
||||||
{ config, ... }:
|
{ config, ... }:
|
||||||
{
|
{
|
||||||
|
|
||||||
distributedServices = clanLib.inventory.mapInstances {
|
distributedServices = clanLib.inventory.mapInstances {
|
||||||
inherit (config) inventory;
|
inherit (config) inventory;
|
||||||
inherit localModuleSet;
|
inherit localModuleSet;
|
||||||
inherit flakeInputs;
|
inherit flakeInputs;
|
||||||
prefix = prefix ++ [ "distributedServices" ];
|
prefix = prefix ++ [ "distributedServices" ];
|
||||||
};
|
};
|
||||||
machines = lib.mapAttrs (_machineName: v: {
|
machines = config.distributedServices.allMachines;
|
||||||
machineImports = v;
|
|
||||||
}) config.distributedServices.allMachines;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -410,6 +410,49 @@ in
|
|||||||
default = { };
|
default = { };
|
||||||
type = clanLib.types.uniqueDeferredSerializableModule;
|
type = clanLib.types.uniqueDeferredSerializableModule;
|
||||||
};
|
};
|
||||||
|
extraModules = lib.mkOption {
|
||||||
|
description = ''
|
||||||
|
List of additionally imported `.nix` expressions.
|
||||||
|
|
||||||
|
Supported types:
|
||||||
|
|
||||||
|
- **Strings**: Interpreted relative to the 'directory' passed to buildClan.
|
||||||
|
- **Paths**: should be relative to the current file.
|
||||||
|
- **Any**: Nix expression must be serializable to JSON.
|
||||||
|
|
||||||
|
!!! Note
|
||||||
|
**The import only happens if the machine is part of the service or role.**
|
||||||
|
|
||||||
|
Other types are passed through to the nixos configuration.
|
||||||
|
|
||||||
|
???+ Example
|
||||||
|
To import the `special.nix` file
|
||||||
|
|
||||||
|
```
|
||||||
|
. Clan Directory
|
||||||
|
├── flake.nix
|
||||||
|
...
|
||||||
|
└── modules
|
||||||
|
├── special.nix
|
||||||
|
└── ...
|
||||||
|
```
|
||||||
|
|
||||||
|
```nix
|
||||||
|
{
|
||||||
|
extraModules = [ "modules/special.nix" ];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
'';
|
||||||
|
apply = value: if lib.isString value then value else builtins.seq (builtins.toJSON value) value;
|
||||||
|
default = [ ];
|
||||||
|
type = types.listOf (
|
||||||
|
types.oneOf [
|
||||||
|
types.str
|
||||||
|
types.path
|
||||||
|
(types.attrsOf types.anything)
|
||||||
|
]
|
||||||
|
);
|
||||||
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -114,7 +114,9 @@ in
|
|||||||
};
|
};
|
||||||
in
|
in
|
||||||
# instances.<instanceName>.roles.<roleName> =
|
# instances.<instanceName>.roles.<roleName> =
|
||||||
{
|
# Remove "tags", they are resolved into "machines"
|
||||||
|
(removeAttrs role [ "tags" ])
|
||||||
|
// {
|
||||||
machines = lib.genAttrs resolvedMachines.machines (
|
machines = lib.genAttrs resolvedMachines.machines (
|
||||||
machineName:
|
machineName:
|
||||||
let
|
let
|
||||||
@@ -136,10 +138,6 @@ in
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
# Maps to settings for the role.
|
|
||||||
# In other words this sets the following path of a clan.service module:
|
|
||||||
# instances.<instanceName>.roles.<roleName>.settings
|
|
||||||
settings = role.settings;
|
|
||||||
}
|
}
|
||||||
) instance.roles;
|
) instance.roles;
|
||||||
in
|
in
|
||||||
@@ -157,6 +155,7 @@ in
|
|||||||
modules =
|
modules =
|
||||||
[
|
[
|
||||||
# Import the resolved module.
|
# Import the resolved module.
|
||||||
|
# i.e. clan.modules.admin
|
||||||
(builtins.head instances).instance.resolvedModule
|
(builtins.head instances).instance.resolvedModule
|
||||||
] # Include all the instances that correlate to the resolved module
|
] # Include all the instances that correlate to the resolved module
|
||||||
++ (builtins.map (v: {
|
++ (builtins.map (v: {
|
||||||
@@ -185,20 +184,18 @@ in
|
|||||||
}
|
}
|
||||||
) { } importedModuleWithInstances;
|
) { } importedModuleWithInstances;
|
||||||
|
|
||||||
# TODO: Return an attribute set of resources instead of a plain list of nixosModules
|
allMachines = lib.mapAttrs (machineName: _: {
|
||||||
allMachines = lib.foldlAttrs (
|
# This is the list of nixosModules for each machine
|
||||||
|
machineImports = lib.foldlAttrs (
|
||||||
acc: _module_ident: eval:
|
acc: _module_ident: eval:
|
||||||
acc
|
acc ++ [ eval.config.result.final.${machineName}.nixosModule or { } ]
|
||||||
// lib.mapAttrs (
|
) [ ] importedModulesEvaluated;
|
||||||
machineName: result: acc.${machineName} or [ ] ++ [ result.nixosModule ]
|
}) inventory.machines or { };
|
||||||
) eval.config.result.final
|
|
||||||
) { } importedModulesEvaluated;
|
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
inherit
|
inherit
|
||||||
importedModuleWithInstances
|
importedModuleWithInstances
|
||||||
grouped
|
grouped
|
||||||
|
|
||||||
allMachines
|
allMachines
|
||||||
importedModulesEvaluated
|
importedModulesEvaluated
|
||||||
;
|
;
|
||||||
|
|||||||
@@ -214,6 +214,11 @@ in
|
|||||||
description = "Settings of 'role': ${name}";
|
description = "Settings of 'role': ${name}";
|
||||||
default = { };
|
default = { };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
options.extraModules = lib.mkOption {
|
||||||
|
default = [ ];
|
||||||
|
type = types.listOf (types.deferredModule);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
];
|
];
|
||||||
@@ -405,12 +410,43 @@ in
|
|||||||
# Intermediate result by mapping over the 'roles', 'instances', and 'machines'.
|
# Intermediate result by mapping over the 'roles', 'instances', and 'machines'.
|
||||||
# During this step the 'perMachine' and 'perInstance' are applied.
|
# During this step the 'perMachine' and 'perInstance' are applied.
|
||||||
# The result-set for a single machine can then be found by collecting all 'nixosModules' recursively.
|
# The result-set for a single machine can then be found by collecting all 'nixosModules' recursively.
|
||||||
|
|
||||||
|
/**
|
||||||
|
allRoles :: {
|
||||||
|
<roleName> :: {
|
||||||
|
allInstances :: {
|
||||||
|
<instanceName> :: {
|
||||||
|
allMachines :: {
|
||||||
|
<machineName> :: {
|
||||||
|
nixosModule :: NixOSModule;
|
||||||
|
services :: { }; # TODO: nested services
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
*/
|
||||||
result.allRoles = mkOption {
|
result.allRoles = mkOption {
|
||||||
readOnly = true;
|
readOnly = true;
|
||||||
default = lib.mapAttrs (roleName: roleCfg: {
|
default = lib.mapAttrs (roleName: roleCfg: {
|
||||||
allInstances = lib.mapAttrs (instanceName: instanceCfg: {
|
allInstances = lib.mapAttrs (instanceName: instanceCfg: {
|
||||||
allMachines = lib.mapAttrs (
|
allMachines = lib.mapAttrs (
|
||||||
machineName: _machineCfg: roleCfg.perInstance instanceName machineName
|
machineName: _machineCfg:
|
||||||
|
let
|
||||||
|
instanceRes = roleCfg.perInstance instanceName machineName;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
nixosModule = {
|
||||||
|
imports = [
|
||||||
|
# Result of the applied 'perInstance = {...}: { nixosModule = { ... }; }'
|
||||||
|
instanceRes.nixosModule
|
||||||
|
] ++ instanceCfg.roles.${roleName}.extraModules;
|
||||||
|
};
|
||||||
|
# TODO: nested services
|
||||||
|
services = { };
|
||||||
|
}
|
||||||
|
|
||||||
) instanceCfg.roles.${roleName}.machines or { };
|
) instanceCfg.roles.${roleName}.machines or { };
|
||||||
}) config.instances;
|
}) config.instances;
|
||||||
}) config.roles;
|
}) config.roles;
|
||||||
@@ -434,6 +470,9 @@ in
|
|||||||
) [ ] instance.roles
|
) [ ] instance.roles
|
||||||
);
|
);
|
||||||
# The service machines are defined by collecting all instance machines
|
# The service machines are defined by collecting all instance machines
|
||||||
|
# returns "allMachines" that are part of the service in the form:
|
||||||
|
# serviceMachines :: { ${machineName} :: MachineOrigin; }
|
||||||
|
# MachineOrigin :: { instances :: [ string ]; roles :: [ string ]; }
|
||||||
serviceMachines = lib.foldlAttrs (
|
serviceMachines = lib.foldlAttrs (
|
||||||
acc: instanceName: instance:
|
acc: instanceName: instance:
|
||||||
acc
|
acc
|
||||||
@@ -470,8 +509,6 @@ in
|
|||||||
default = lib.mapAttrs (
|
default = lib.mapAttrs (
|
||||||
machineName: machineResult:
|
machineName: machineResult:
|
||||||
let
|
let
|
||||||
# config.result.allRoles.client.allInstances.bar.allMachines.test
|
|
||||||
# instanceResults = config.result.allRoles.client.allInstances.bar.allMachines.${machineName};
|
|
||||||
instanceResults = lib.foldlAttrs (
|
instanceResults = lib.foldlAttrs (
|
||||||
acc: roleName: role:
|
acc: roleName: role:
|
||||||
acc
|
acc
|
||||||
|
|||||||
@@ -88,25 +88,37 @@ let
|
|||||||
roles.peer.tags.all = { };
|
roles.peer.tags.all = { };
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
1 { imports = [ { instanceName = "instance_foo"; machine = { name = "jon"; roles = [ "controller" "pe 1 null
|
||||||
|
. er" ]; }; roles = { controller = { machines = { jon = { settings = { }; }; }; settings = { }; }; pe .
|
||||||
|
. er = { machines = { jon = { settings = { timeout = "foo-peer-jon"; }; }; }; settings = { timeout = .
|
||||||
|
. "foo-peer"; }; }; }; settings = { timeout = "foo-peer-jon"; }; vendoredSettings = { timeout = "conf .
|
||||||
|
. 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 =
|
||||||
instanceName =
|
let
|
||||||
res.importedModulesEvaluated.self-A.config.result.allRoles.peer.allInstances."instance_foo".allMachines.jon.nixosModule.instanceName;
|
m = (
|
||||||
|
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 =
|
settings = m.settings;
|
||||||
res.importedModulesEvaluated.self-A.config.result.allRoles.peer.allInstances.instance_foo.allMachines.jon.nixosModule.settings;
|
machine = m.machine;
|
||||||
machine =
|
roles = m.roles;
|
||||||
res.importedModulesEvaluated.self-A.config.result.allRoles.peer.allInstances.instance_foo.allMachines.jon.nixosModule.machine;
|
|
||||||
roles =
|
|
||||||
res.importedModulesEvaluated.self-A.config.result.allRoles.peer.allInstances.instance_foo.allMachines.jon.nixosModule.roles;
|
|
||||||
};
|
};
|
||||||
expected = {
|
expected = {
|
||||||
instanceName = "instance_foo";
|
instanceName = "instance_foo";
|
||||||
@@ -147,10 +159,12 @@ in
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# TODO: Cannot be tested like this anymore
|
||||||
test_per_instance_settings_vendoring = {
|
test_per_instance_settings_vendoring = {
|
||||||
expr =
|
expr =
|
||||||
|
(unwrapModule
|
||||||
res.importedModulesEvaluated.self-A.config.result.allRoles.peer.allInstances."instance_foo".allMachines.jon.nixosModule.vendoredSettings;
|
res.importedModulesEvaluated.self-A.config.result.allRoles.peer.allInstances."instance_foo".allMachines.jon.nixosModule
|
||||||
|
).vendoredSettings;
|
||||||
expected = {
|
expected = {
|
||||||
timeout = "config.thing";
|
timeout = "config.thing";
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -38,11 +38,13 @@ class InventoryInstanceRoleTag(TypedDict):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
InventoryInstanceRoleExtramodulesType = list[dict[str, Any] | str]
|
||||||
InventoryInstanceRoleMachinesType = dict[str, InventoryInstanceRoleMachine]
|
InventoryInstanceRoleMachinesType = dict[str, InventoryInstanceRoleMachine]
|
||||||
InventoryInstanceRoleSettingsType = Unknown
|
InventoryInstanceRoleSettingsType = Unknown
|
||||||
InventoryInstanceRoleTagsType = dict[str, InventoryInstanceRoleTag]
|
InventoryInstanceRoleTagsType = dict[str, InventoryInstanceRoleTag]
|
||||||
|
|
||||||
class InventoryInstanceRole(TypedDict):
|
class InventoryInstanceRole(TypedDict):
|
||||||
|
extraModules: NotRequired[InventoryInstanceRoleExtramodulesType]
|
||||||
machines: NotRequired[InventoryInstanceRoleMachinesType]
|
machines: NotRequired[InventoryInstanceRoleMachinesType]
|
||||||
settings: NotRequired[InventoryInstanceRoleSettingsType]
|
settings: NotRequired[InventoryInstanceRoleSettingsType]
|
||||||
tags: NotRequired[InventoryInstanceRoleTagsType]
|
tags: NotRequired[InventoryInstanceRoleTagsType]
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import argparse
|
import argparse
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import sys
|
import traceback
|
||||||
from collections.abc import Callable, Iterable
|
from collections.abc import Callable, Iterable
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -38,6 +38,7 @@ def map_json_type(
|
|||||||
for t in json_type:
|
for t in json_type:
|
||||||
res.extend(map_json_type(t))
|
res.extend(map_json_type(t))
|
||||||
return sort_types(set(res))
|
return sort_types(set(res))
|
||||||
|
|
||||||
if isinstance(json_type, dict):
|
if isinstance(json_type, dict):
|
||||||
items = json_type.get("items")
|
items = json_type.get("items")
|
||||||
if items:
|
if items:
|
||||||
@@ -46,6 +47,13 @@ def map_json_type(
|
|||||||
if not json_type.get("type") and json_type.get("tsType") == "unknown":
|
if not json_type.get("type") and json_type.get("tsType") == "unknown":
|
||||||
return ["Unknown"]
|
return ["Unknown"]
|
||||||
|
|
||||||
|
union = json_type.get("oneOf")
|
||||||
|
if union:
|
||||||
|
res: list[str] = []
|
||||||
|
for t in union:
|
||||||
|
res.extend(map_json_type(t, nested_types, parent))
|
||||||
|
return sort_types(set(res))
|
||||||
|
|
||||||
return sort_types(map_json_type(json_type.get("type"), nested_types))
|
return sort_types(map_json_type(json_type.get("type"), nested_types))
|
||||||
if json_type == "string":
|
if json_type == "string":
|
||||||
return ["str"]
|
return ["str"]
|
||||||
@@ -67,6 +75,7 @@ def map_json_type(
|
|||||||
return [f"""dict[str, {" | ".join(sort_types(nested_types))}]"""]
|
return [f"""dict[str, {" | ".join(sort_types(nested_types))}]"""]
|
||||||
if json_type == "null":
|
if json_type == "null":
|
||||||
return ["None"]
|
return ["None"]
|
||||||
|
|
||||||
msg = f"Python type not found for {json_type}"
|
msg = f"Python type not found for {json_type}"
|
||||||
raise Error(msg)
|
raise Error(msg)
|
||||||
|
|
||||||
@@ -432,15 +441,13 @@ def main() -> None:
|
|||||||
default=None,
|
default=None,
|
||||||
)
|
)
|
||||||
parser.set_defaults(func=run_gen)
|
parser.set_defaults(func=run_gen)
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
try:
|
|
||||||
args.func(args)
|
args.func(args)
|
||||||
except Error as e:
|
|
||||||
print(e)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
try:
|
||||||
main()
|
main()
|
||||||
|
except Exception:
|
||||||
|
print("An error occurred:")
|
||||||
|
traceback.print_exc()
|
||||||
|
|||||||
Reference in New Issue
Block a user