feat(registry): validate unique addresses and routes

This commit was merged in pull request #66.
This commit is contained in:
2026-08-25 22:29:56 -04:00
parent f45aa44ead
commit 48d9d3280f
5 changed files with 117 additions and 69 deletions

127
flake.nix
View File

@@ -403,6 +403,38 @@
touch $out touch $out
''; '';
address-registry-duplicate-value-fails =
let
alpha = lib.nixosSystem {
system = "x86_64-linux";
specialArgs.my = self.lib;
modules = [
self.lib.registry.module
{
registry.address.alpha.self = "10.23.50.146";
system.stateVersion = "26.11";
}
];
};
beta = lib.nixosSystem {
system = "x86_64-linux";
specialArgs.my = self.lib;
modules = [
self.lib.registry.module
{
registry.address.beta.self = "10.23.50.146";
system.stateVersion = "26.11";
}
];
};
registry = self.lib.registry.collect { hosts = { inherit alpha beta; }; };
result = builtins.tryEval (builtins.deepSeq registry.address true);
in
pkgs.runCommand "address-registry-duplicate-value-fails-check" { } ''
${lib.optionalString result.success "exit 1"}
touch $out
'';
ssh-targets-use-address-registry = ssh-targets-use-address-registry =
let let
neogaiaConfig = self.nixosConfigurations.neogaia.config; neogaiaConfig = self.nixosConfigurations.neogaia.config;
@@ -434,7 +466,7 @@
modules = [ modules = [
self.lib.registry.module self.lib.registry.module
{ {
registry.address.routeHost.app = "10.23.20.126"; registry.address.routeHost.app = "10.23.20.127";
registry.route.routeHost.app = { registry.route.routeHost.app = {
domain = "worker.alexion.dev"; domain = "worker.alexion.dev";
port = 3923; port = 3923;
@@ -472,7 +504,7 @@
registry.route ? routeHost registry.route ? routeHost
&& registry.route.routeHost ? app && registry.route.routeHost ? app
&& registry.route.pikachu ? actualbudget && registry.route.pikachu ? actualbudget
&& vhosts."worker.alexion.dev".locations."/".proxyPass == "http://10.23.20.126:3923" && vhosts."worker.alexion.dev".locations."/".proxyPass == "http://10.23.20.127:3923"
&& vhosts."budget.alexion.dev".locations."/".proxyPass == "http://10.23.20.42:5006" && vhosts."budget.alexion.dev".locations."/".proxyPass == "http://10.23.20.42:5006"
&& vhosts."files.alexion.dev".locations."/".proxyPass == "http://10.23.20.126:3923" && vhosts."files.alexion.dev".locations."/".proxyPass == "http://10.23.20.126:3923"
) )
@@ -481,6 +513,44 @@
touch $out touch $out
''; '';
route-registry-duplicate-domain-fails =
let
alpha = lib.nixosSystem {
system = "x86_64-linux";
specialArgs.my = self.lib;
modules = [
self.lib.registry.module
{
registry.route.alpha.app = {
domain = "files.alexion.dev";
port = 3923;
};
system.stateVersion = "26.11";
}
];
};
beta = lib.nixosSystem {
system = "x86_64-linux";
specialArgs.my = self.lib;
modules = [
self.lib.registry.module
{
registry.route.beta.app = {
domain = "files.alexion.dev";
port = 3923;
};
system.stateVersion = "26.11";
}
];
};
registry = self.lib.registry.collect { hosts = { inherit alpha beta; }; };
result = builtins.tryEval (builtins.deepSeq registry.route true);
in
pkgs.runCommand "route-registry-duplicate-domain-fails-check" { } ''
${lib.optionalString result.success "exit 1"}
touch $out
'';
reverse-proxy-address-backends = reverse-proxy-address-backends =
let let
workerHost = workerHost =
@@ -603,59 +673,6 @@
touch $out touch $out
''; '';
reverse-proxy-duplicate-domain-fails =
let
pikachu = lib.nixosSystem {
system = "x86_64-linux";
specialArgs.my = self.lib;
modules = [
self.lib.registry.module
{
registry.address.pikachu.copyparty = "10.23.20.126";
registry.route.pikachu.copyparty = {
domain = "files.alexion.dev";
port = 3923;
};
system.stateVersion = "26.11";
}
];
};
raichu = lib.nixosSystem {
system = "x86_64-linux";
specialArgs.my = self.lib;
modules = [
self.lib.registry.module
{
registry.address.raichu.files = "10.23.20.10";
registry.route.raichu.files = {
domain = "files.alexion.dev";
port = 3923;
};
system.stateVersion = "26.11";
}
];
};
registry = self.lib.registry.collect { hosts = { inherit pikachu raichu; }; };
testConfig = lib.nixosSystem {
system = "x86_64-linux";
specialArgs = {
my = self.lib;
inherit registry;
};
modules = [
./modules/reverse-proxy.nix
{
modules.reverse-proxy.enable = true;
system.stateVersion = "26.11";
}
];
};
result = builtins.tryEval (builtins.deepSeq testConfig.config.services.nginx.virtualHosts true);
in
pkgs.runCommand "reverse-proxy-duplicate-domain-fails-check" { } ''
${lib.optionalString result.success "exit 1"}
touch $out
'';
}; };
}; };
} }

View File

@@ -16,26 +16,37 @@ let
nameValuePair nameValuePair
removeSuffix removeSuffix
types types
unique
; ;
assertionType = types.submodule { checkType = types.submodule {
options = { options = {
assertion = mkOption { assertion = mkOption {
type = types.bool; type = types.functionTo types.bool;
description = '' description = ''
Whether this registry validation assertion passed. Predicate run against this registry's flattened collected entries.
''; '';
}; };
message = mkOption { message = mkOption {
type = types.str; type = types.str;
description = '' description = ''
Error message shown when this registry validation assertion fails. Error message shown when this registry validation check fails.
''; '';
}; };
}; };
}; };
predicates = {
uniqueValue = value: entries: builtins.length (unique (map value entries)) == builtins.length entries;
};
registryLib = lib // {
registry = (lib.registry or { }) // {
inherit predicates;
};
};
registryDefinitionModule = { registryDefinitionModule = {
options = { options = {
type = mkOption { type = mkOption {
@@ -53,11 +64,11 @@ let
''; '';
}; };
validate = mkOption { checks = mkOption {
type = types.functionTo (types.listOf assertionType); type = types.listOf checkType;
default = _entries: [ ]; default = [ ];
description = '' description = ''
Validate this registry's flattened collected entries. Validation checks for this registry's flattened collected entries.
''; '';
}; };
}; };
@@ -76,7 +87,7 @@ let
mkRegistry = path: mkRegistry = path:
let let
evaluated = lib.evalModules { evaluated = lib.evalModules {
specialArgs = { inherit lib; }; specialArgs.lib = registryLib;
modules = [ modules = [
registryDefinitionModule registryDefinitionModule
path path
@@ -84,7 +95,7 @@ let
}; };
in in
{ {
inherit (evaluated.config) type replicate validate; inherit (evaluated.config) type replicate checks;
}; };
definitions = mapAttrs' ( definitions = mapAttrs' (
@@ -149,7 +160,13 @@ let
concatLists ( concatLists (
mapAttrsToList ( mapAttrsToList (
registryName: registry: registryName: registry:
registry.validate (flattenRegistryEntries registryName data.${registryName}) let
entries = flattenRegistryEntries registryName data.${registryName};
in
map (check: {
assertion = check.assertion entries;
inherit (check) message;
}) registry.checks
) definitions ) definitions
); );
@@ -189,7 +206,7 @@ let
}; };
in in
{ {
inherit definitions; inherit definitions predicates;
module = { module = {
options.registry = mapAttrs (_name: registry: registryOption registry) definitions; options.registry = mapAttrs (_name: registry: registryOption registry) definitions;

View File

@@ -1,4 +1,11 @@
{ lib, ... }: { lib, ... }:
{ {
type = lib.types.str; type = lib.types.str;
checks = [
{
assertion = lib.registry.predicates.uniqueValue (entry: entry.value);
message = "Address registry values must be unique.";
}
];
} }

View File

@@ -1,5 +1,12 @@
{ lib, ... }: { lib, ... }:
{ {
checks = [
{
assertion = lib.registry.predicates.uniqueValue (entry: entry.value.domain);
message = "Route domains must be unique.";
}
];
type = lib.types.submodule { type = lib.types.submodule {
options = { options = {
domain = lib.mkOption { domain = lib.mkOption {

View File

@@ -22,9 +22,9 @@ in
type = lib.types.ints.positive; type = lib.types.ints.positive;
validate = entries: [ checks = [
{ {
assertion = duplicateUids entries == { }; assertion = entries: duplicateUids entries == { };
message = "Stable UID allocations must be unique."; message = "Stable UID allocations must be unique.";
} }
]; ];