{ hosts, lib, root, }: let definitions = lib.mapAttrs' ( name: _type: let path = root + "/${name}"; in lib.nameValuePair (registryName name) (mkRegistry path) ) ( lib.filterAttrs (name: type: type == "regular" && lib.hasSuffix ".nix" name) (builtins.readDir root) ); registryName = fileName: lib.removeSuffix ".nix" fileName; mkRegistry = path: let evaluated = lib.evalModules { specialArgs.lib = registryLib; modules = [ registryDefinitionModule path ]; }; in { inherit (evaluated.config) type replicate checks; }; registryLib = lib // { registry = (lib.registry or { }) // { inherit predicates; }; }; predicates = { uniqueValue = value: entries: builtins.length (lib.unique (map value entries)) == builtins.length entries; }; registryDefinitionModule = { options = { type = lib.mkOption { type = lib.types.raw; description = '' NixOS option type for one registry entry. ''; }; replicate = lib.mkOption { type = lib.types.bool; default = false; description = '' Whether collected entries are merged and published under every host key. ''; }; checks = lib.mkOption { type = lib.types.listOf checkType; default = [ ]; description = '' Validation checks for this registry's flattened collected entries. ''; }; }; }; checkType = lib.types.submodule { options = { assertion = lib.mkOption { type = lib.types.functionTo lib.types.bool; description = '' Predicate run against this registry's flattened collected entries. ''; }; message = lib.mkOption { type = lib.types.str; description = '' Error message shown when this registry validation check fails. ''; }; }; }; registryOption = registry: lib.mkOption { type = lib.types.attrsOf (lib.types.attrsOf registry.type); default = { }; description = '' Lazily collected fleet-visible registry entries, nested by host and entry name. ''; }; withOperations = registry: registry // { join = host: entry: join registry host entry; }; join = registry: host: entry: lib.filterAttrs (_name: value: value != null) ( lib.mapAttrs ( registryName: _registry: (registry.${registryName}.${host} or { }).${entry} or null ) definitions ); collectData = { hosts }: let data = lib.mapAttrs ( registryName: registry: if registry.replicate or false then replicatedData registryName hosts raw.${registryName} else lib.mapAttrs (hostName: host: host.config.registry.${registryName}.${hostName} or { }) hosts ) definitions; raw = lib.mapAttrs (registryName: _registry: rawDeclarations registryName hosts) definitions; in assertValid data; replicatedData = registryName: hosts: raw: let entries = lib.mapAttrs (_entryName: entry: entry.value) (replicatedEntries registryName raw); in lib.mapAttrs (_hostName: _host: entries) hosts; replicatedEntries = registryName: raw: let entries = lib.concatLists ( lib.concatLists ( lib.mapAttrsToList ( _evalHost: hostDeclarations: lib.mapAttrsToList ( hostName: declarations: lib.mapAttrsToList (entryName: value: { path = "registry.${registryName}.${hostName}.${entryName}"; inherit entryName value; }) declarations ) hostDeclarations ) raw ) ); in lib.foldl' ( acc: entry: if lib.hasAttr entry.entryName acc then if acc.${entry.entryName}.value == entry.value then acc else throw "conflicting replicated registry entry ${entry.path} does not match ${acc.${entry.entryName}.path}" else acc // { ${entry.entryName} = entry; } ) { } entries; rawDeclarations = registryName: hosts: lib.mapAttrs (_hostName: host: host.config.registry.${registryName} or { }) hosts; assertValid = data: let assertions = validationAssertions data; failures = lib.filter (assertion: !assertion.assertion) assertions; in assert lib.assertMsg (lib.all (assertion: assertion.assertion) assertions) ( lib.concatMapStringsSep "\n" (assertion: assertion.message) failures ); data; validationAssertions = data: lib.concatLists ( lib.mapAttrsToList ( registryName: registry: let entries = flattenRegistryEntries registryName data.${registryName}; in map (check: { assertion = check.assertion entries; inherit (check) message; }) registry.checks ) definitions ); flattenRegistryEntries = registryName: data: lib.concatLists ( lib.mapAttrsToList ( host: entries: lib.mapAttrsToList (entry: value: { path = "registry.${registryName}.${host}.${entry}"; inherit host entry value; }) entries ) data ); in { types = lib.mapAttrs (_name: definition: definition.type) definitions; module = { options.registry = lib.mapAttrs (_name: registryOption) definitions; }; collected = withOperations (collectData { inherit hosts; }); }