Merge pull request 'Inventory: init inventory.tags for globally defined static and dynamic tags' (#2328) from hsjobeki/clan-core:hsjobeki-main into main
This commit is contained in:
@@ -13,6 +13,7 @@ in
|
||||
{
|
||||
|
||||
options.clan = lib.mkOption {
|
||||
default = { };
|
||||
type = types.submoduleWith {
|
||||
specialArgs = {
|
||||
inherit clan-core self;
|
||||
|
||||
33
lib/build-clan/computed-tags.nix
Normal file
33
lib/build-clan/computed-tags.nix
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
{
|
||||
config.inventory = {
|
||||
tags = (
|
||||
{ machines, ... }:
|
||||
{
|
||||
# Only compute the default value
|
||||
# The option MUST be defined in ./build-inventory/interface.nix
|
||||
all = lib.mkDefault (builtins.attrNames machines);
|
||||
}
|
||||
);
|
||||
};
|
||||
# Add the computed tags to machine tags for displaying them
|
||||
options.inventory = {
|
||||
machines = lib.mkOption {
|
||||
type = lib.types.attrsOf (
|
||||
lib.types.submodule (
|
||||
# 'name' is the machines attribute-name
|
||||
{ name, ... }:
|
||||
{
|
||||
tags = builtins.attrNames (
|
||||
lib.filterAttrs (_t: tagMemers: builtins.elem name tagMemers) config.inventory.tags
|
||||
);
|
||||
}
|
||||
)
|
||||
);
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -162,6 +162,8 @@ in
|
||||
#
|
||||
# config.inventory.meta <- config.meta
|
||||
{ inventory.meta = config.meta; }
|
||||
# Set default for computed tags
|
||||
./computed-tags.nix
|
||||
];
|
||||
|
||||
inherit nixosConfigurations;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{ lib, ... }:
|
||||
{ lib, config, ... }:
|
||||
let
|
||||
types = lib.types;
|
||||
|
||||
@@ -88,7 +88,9 @@ let
|
||||
in
|
||||
{
|
||||
|
||||
imports = [ ./assertions.nix ];
|
||||
imports = [
|
||||
./assertions.nix
|
||||
];
|
||||
options = {
|
||||
assertions = lib.mkOption {
|
||||
type = types.listOf types.unspecified;
|
||||
@@ -103,6 +105,81 @@ in
|
||||
];
|
||||
};
|
||||
};
|
||||
tags = lib.mkOption {
|
||||
default = { };
|
||||
description = ''
|
||||
Tags of the inventory are used to group machines together.
|
||||
|
||||
It is recommended to use [`machine.tags`](#machinestags) to define the tags of the machines.
|
||||
|
||||
This can be used to define custom tags that are either statically set or dynamically computed.
|
||||
|
||||
#### Static Tags
|
||||
|
||||
???+ example "Static Tag Example"
|
||||
```nix
|
||||
inventory.tags = {
|
||||
foo = [ "machineA" "machineB" ];
|
||||
};
|
||||
```
|
||||
|
||||
The tag `foo` will always be added to `machineA` and `machineB`.
|
||||
|
||||
#### Dynamic Tags
|
||||
|
||||
It is possible to compute tags based on the machines properties or based on other tags.
|
||||
|
||||
!!! danger
|
||||
This is a powerfull feature and should be used with caution.
|
||||
|
||||
It is possible to cause infinite recursion by computing tags based on the machines properties or based on other tags.
|
||||
|
||||
???+ example "Dynamic Tag Example"
|
||||
|
||||
allButFoo is a computed tag. It will be added to all machines except 'foo'
|
||||
|
||||
`all` is a predefined tag. See the docs of [`tags.all`](#tagsall).
|
||||
|
||||
```nix
|
||||
# inventory.tags ↓ ↓ inventory.machines
|
||||
inventory.tags = {config, machines...}: {
|
||||
# ↓↓↓ The "all" tag
|
||||
allButFoo = builtins.filter (name: name != "foo") config.all;
|
||||
};
|
||||
```
|
||||
|
||||
!!! warning
|
||||
Do NOT compute `tags` from `machine.tags` this will cause infinite recursion.
|
||||
'';
|
||||
type = types.submoduleWith {
|
||||
specialArgs = {
|
||||
inherit (config) machines;
|
||||
};
|
||||
modules = [
|
||||
{
|
||||
freeformType = with lib.types; lazyAttrsOf (listOf str);
|
||||
# Reserved tags
|
||||
# Defined as options here to show them in advance
|
||||
options = {
|
||||
# 'All machines' tag
|
||||
all = lib.mkOption {
|
||||
type = with lib.types; listOf str;
|
||||
defaultText = "[ <All Machines> ]";
|
||||
description = ''
|
||||
!!! example "Predefined Tag"
|
||||
|
||||
Will be added to all machines
|
||||
|
||||
```nix
|
||||
inventory.machines.machineA.tags = [ "all" ];
|
||||
```
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
machines = lib.mkOption {
|
||||
description = ''
|
||||
|
||||
@@ -30,7 +30,10 @@ let
|
||||
|
||||
filterExcluded = lib.filter (opt: !isExcludedOption opt);
|
||||
|
||||
filterExcludedAttrs = lib.filterAttrs (_name: opt: !isExcludedOption opt);
|
||||
excludedOptionNames = [ "_freeformOptions" ];
|
||||
filterExcludedAttrs = lib.filterAttrs (
|
||||
name: opt: !isExcludedOption opt && !builtins.elem name excludedOptionNames
|
||||
);
|
||||
|
||||
# Filter out options where the visible attribute is set to false
|
||||
filterInvisibleOpts = lib.filterAttrs (_name: opt: opt.visible or true);
|
||||
@@ -95,6 +98,27 @@ rec {
|
||||
requiredProps = lib.filterAttrs (_: prop: isRequired prop) properties;
|
||||
required = lib.optionalAttrs (requiredProps != { }) { required = lib.attrNames requiredProps; };
|
||||
header' = if addHeader then header else { };
|
||||
|
||||
# freeformType is a special type
|
||||
freeformDefs = (options._module.freeformType.definitions or [ ]);
|
||||
checkFreeformDefs =
|
||||
defs:
|
||||
if (builtins.length defs) != 1 then
|
||||
throw "parseOptions: freeformType definitions not supported"
|
||||
else
|
||||
defs;
|
||||
# It seems that freeformType has [ null ]
|
||||
freeformProperties =
|
||||
if freeformDefs != [ ] && builtins.head freeformDefs != null then
|
||||
# freeformType has only one definition
|
||||
parseOption {
|
||||
# options._module.freeformType.definitions
|
||||
type = (builtins.head (checkFreeformDefs freeformDefs));
|
||||
_type = "option";
|
||||
loc = options._module.freeformType.loc;
|
||||
}
|
||||
else
|
||||
{ };
|
||||
in
|
||||
# return jsonschema
|
||||
header'
|
||||
@@ -103,7 +127,8 @@ rec {
|
||||
type = "object";
|
||||
inherit properties;
|
||||
additionalProperties = false;
|
||||
};
|
||||
}
|
||||
// freeformProperties;
|
||||
|
||||
# parses and evaluated nixos option to a jsonschema property definition
|
||||
parseOption =
|
||||
|
||||
@@ -24,7 +24,6 @@ let
|
||||
in
|
||||
evaledConfig.options.opt;
|
||||
in
|
||||
|
||||
{
|
||||
testNoDefaultNoDescription =
|
||||
let
|
||||
|
||||
@@ -36,4 +36,41 @@
|
||||
type = "object";
|
||||
};
|
||||
};
|
||||
|
||||
testFreeFormOfInt =
|
||||
let
|
||||
default = {
|
||||
foo = 1;
|
||||
bar = 2;
|
||||
};
|
||||
in
|
||||
{
|
||||
expr = slib.parseOptions (lib.evalModules {
|
||||
modules = [
|
||||
{
|
||||
freeformType = with lib.types; attrsOf int;
|
||||
options = {
|
||||
enable = lib.mkEnableOption "enable this";
|
||||
};
|
||||
}
|
||||
default
|
||||
];
|
||||
}).options { };
|
||||
expected = {
|
||||
"$schema" = "http://json-schema.org/draft-07/schema#";
|
||||
additionalProperties = {
|
||||
type = "integer";
|
||||
};
|
||||
properties = {
|
||||
enable = {
|
||||
default = false;
|
||||
description = "Whether to enable enable this.";
|
||||
examples = [ true ];
|
||||
type = "boolean";
|
||||
};
|
||||
};
|
||||
type = "object";
|
||||
};
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -36,3 +36,4 @@ class Inventory:
|
||||
meta: Meta
|
||||
machines: dict[str, Machine] = field(default_factory = dict)
|
||||
services: dict[str, Service] = field(default_factory = dict)
|
||||
tags: dict[str, list[str]] = field(default_factory = dict)
|
||||
|
||||
@@ -88,6 +88,7 @@
|
||||
buildInputs = [
|
||||
pkgs.python3
|
||||
pkgs.json2ts
|
||||
pkgs.jq
|
||||
];
|
||||
|
||||
installPhase = ''
|
||||
@@ -98,7 +99,9 @@
|
||||
json2ts --input $out/API.json > $out/API.ts
|
||||
|
||||
# Retrieve python API Typescript types
|
||||
json2ts --input ${self'.legacyPackages.schemas.inventory}/schema.json > $out/Inventory.ts
|
||||
# delete the reserved tags from typechecking because the conversion library doesn't support them
|
||||
jq 'del(.properties.tags.properties)' ${self'.legacyPackages.schemas.inventory}/schema.json > schema.json
|
||||
json2ts --input schema.json > $out/Inventory.ts
|
||||
cp ${self'.legacyPackages.schemas.inventory}/* $out
|
||||
'';
|
||||
};
|
||||
|
||||
@@ -24,7 +24,10 @@ def map_json_type(
|
||||
res |= map_json_type(t)
|
||||
return res
|
||||
if isinstance(json_type, dict):
|
||||
return map_json_type(json_type.get("type"))
|
||||
items = json_type.get("items")
|
||||
if items:
|
||||
nested_types = map_json_type(items)
|
||||
return map_json_type(json_type.get("type"), nested_types)
|
||||
if json_type == "string":
|
||||
return {"str"}
|
||||
if json_type == "integer":
|
||||
|
||||
Reference in New Issue
Block a user