diff --git a/src/dotfiles/registry.nix b/src/dotfiles/registry.nix index 4a9314b..116c60a 100644 --- a/src/dotfiles/registry.nix +++ b/src/dotfiles/registry.nix @@ -4,211 +4,26 @@ root, }: let - 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. - ''; - }; + definitionModule = import ./registry/definition-module.nix { inherit lib; }; - message = lib.mkOption { - type = lib.types.str; - description = '' - Error message shown when this registry validation check fails. - ''; - }; - }; + definitions = import ./registry/definitions.nix { + inherit lib root; + inherit (definitionModule) registryDefinitionModule registryLib; }; - predicates = { - uniqueValue = - value: entries: builtins.length (lib.unique (map value entries)) == builtins.length entries; + registryOption = import ./registry/options.nix { inherit lib; }; + + collection = import ./registry/collect.nix { + inherit definitions lib; }; - registryLib = lib // { - registry = (lib.registry or { }) // { - inherit predicates; - }; + validation = import ./registry/validation.nix { + inherit definitions lib; }; - 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. - ''; - }; - }; + operations = import ./registry/operations.nix { + inherit definitions lib; }; - - registryName = fileName: lib.removeSuffix ".nix" fileName; - - 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. - ''; - }; - - mkRegistry = - path: - let - evaluated = lib.evalModules { - specialArgs.lib = registryLib; - modules = [ - registryDefinitionModule - path - ]; - }; - in - { - inherit (evaluated.config) type replicate checks; - }; - - 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) - ); - - flattenRegistryEntries = - registryName: data: - lib.concatLists ( - lib.mapAttrsToList ( - host: entries: - lib.mapAttrsToList (entry: value: { - path = "registry.${registryName}.${host}.${entry}"; - inherit host entry value; - }) entries - ) data - ); - - rawDeclarations = - registryName: hosts: - lib.mapAttrs (_hostName: host: host.config.registry.${registryName} or { }) 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; - - replicatedData = - registryName: hosts: raw: - let - entries = lib.mapAttrs (_entryName: entry: entry.value) (replicatedEntries registryName raw); - in - lib.mapAttrs (_hostName: _host: entries) hosts; - - 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 - ); - - 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; - - 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 - raw = lib.mapAttrs (registryName: _registry: rawDeclarations registryName hosts) definitions; - 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; - in - assertValid data; - - withOperations = - registry: - registry - // { - join = host: entry: join registry host entry; - }; in { types = lib.mapAttrs (_name: definition: definition.type) definitions; @@ -217,7 +32,11 @@ in options.registry = lib.mapAttrs (_name: registryOption) definitions; }; - collected = withOperations (collectData { - inherit hosts; - }); + collected = operations.withOperations ( + validation.assertValid ( + collection.collectData { + inherit hosts; + } + ) + ); } diff --git a/src/dotfiles/registry/collect.nix b/src/dotfiles/registry/collect.nix new file mode 100644 index 0000000..609ba6a --- /dev/null +++ b/src/dotfiles/registry/collect.nix @@ -0,0 +1,67 @@ +{ definitions, lib }: +let + rawDeclarations = + registryName: hosts: + lib.mapAttrs (_hostName: host: host.config.registry.${registryName} or { }) 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; + + replicatedData = + registryName: hosts: raw: + let + entries = lib.mapAttrs (_entryName: entry: entry.value) (replicatedEntries registryName raw); + in + lib.mapAttrs (_hostName: _host: entries) hosts; + + collectData = + { hosts }: + let + raw = lib.mapAttrs (registryName: _registry: rawDeclarations registryName hosts) definitions; + 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; + in + data; +in +{ + inherit + collectData + rawDeclarations + replicatedData + replicatedEntries + ; +} diff --git a/src/dotfiles/registry/definition-module.nix b/src/dotfiles/registry/definition-module.nix new file mode 100644 index 0000000..635a6a5 --- /dev/null +++ b/src/dotfiles/registry/definition-module.nix @@ -0,0 +1,66 @@ +{ lib }: +let + 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. + ''; + }; + }; + }; + + predicates = { + uniqueValue = + value: entries: builtins.length (lib.unique (map value entries)) == builtins.length entries; + }; + + registryLib = lib // { + registry = (lib.registry or { }) // { + inherit predicates; + }; + }; + + 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. + ''; + }; + }; + }; +in +{ + inherit + checkType + predicates + registryDefinitionModule + registryLib + ; +} diff --git a/src/dotfiles/registry/definitions.nix b/src/dotfiles/registry/definitions.nix new file mode 100644 index 0000000..b95741f --- /dev/null +++ b/src/dotfiles/registry/definitions.nix @@ -0,0 +1,35 @@ +{ + lib, + registryDefinitionModule, + registryLib, + root, +}: +let + 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; + }; +in +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) + ) diff --git a/src/dotfiles/registry/operations.nix b/src/dotfiles/registry/operations.nix new file mode 100644 index 0000000..7ceef00 --- /dev/null +++ b/src/dotfiles/registry/operations.nix @@ -0,0 +1,20 @@ +{ definitions, lib }: +let + join = + registry: host: entry: + lib.filterAttrs (_name: value: value != null) ( + lib.mapAttrs ( + registryName: _registry: (registry.${registryName}.${host} or { }).${entry} or null + ) definitions + ); + + withOperations = + registry: + registry + // { + join = host: entry: join registry host entry; + }; +in +{ + inherit join withOperations; +} diff --git a/src/dotfiles/registry/options.nix b/src/dotfiles/registry/options.nix new file mode 100644 index 0000000..312b378 --- /dev/null +++ b/src/dotfiles/registry/options.nix @@ -0,0 +1,9 @@ +{ lib }: +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. + ''; +} diff --git a/src/dotfiles/registry/validation.nix b/src/dotfiles/registry/validation.nix new file mode 100644 index 0000000..d0d6bfb --- /dev/null +++ b/src/dotfiles/registry/validation.nix @@ -0,0 +1,47 @@ +{ definitions, lib }: +let + flattenRegistryEntries = + registryName: data: + lib.concatLists ( + lib.mapAttrsToList ( + host: entries: + lib.mapAttrsToList (entry: value: { + path = "registry.${registryName}.${host}.${entry}"; + inherit host entry value; + }) entries + ) 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 + ); + + 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; +in +{ + inherit + assertValid + flattenRegistryEntries + validationAssertions + ; +}