diff --git a/lib/distributed-service/flake-module.nix b/lib/distributed-service/flake-module.nix index 1169311fa..52f6c7e68 100644 --- a/lib/distributed-service/flake-module.nix +++ b/lib/distributed-service/flake-module.nix @@ -14,7 +14,7 @@ in }: { # Run: nix-unit --extra-experimental-features flakes --flake .#legacyPackages.x86_64-linux. - legacyPackages.evalTest-distributedServices = import ./tests { + legacyPackages.evalTests-distributedServices = import ./tests { inherit lib self; }; @@ -24,7 +24,7 @@ in nix-unit --eval-store "$HOME" \ --extra-experimental-features flakes \ ${inputOverrides} \ - --flake ${self}#legacyPackages.${system}.evalTest-distributedServices + --flake ${self}#legacyPackages.${system}.evalTests-distributedServices touch $out ''; diff --git a/lib/distributed-service/inventory-adapter.nix b/lib/distributed-service/inventory-adapter.nix index fde7bdfee..d99c1bf74 100644 --- a/lib/distributed-service/inventory-adapter.nix +++ b/lib/distributed-service/inventory-adapter.nix @@ -54,7 +54,7 @@ let ) [ ] members.tags or [ ]); }; - machineHasTag = machineName: tagName: lib.elem tagName inventory.machines.${machineName}.tags; + # machineHasTag = machineName: tagName: lib.elem tagName inventory.machines.${machineName}.tags; # map the instances into the module importedModuleWithInstances = lib.mapAttrs ( @@ -119,9 +119,12 @@ let machineName: let machineSettings = instance.roles.${roleName}.machines.${machineName}.settings or { }; - settingsViaTags = lib.filterAttrs ( - tagName: _: machineHasTag machineName tagName - ) instance.roles.${roleName}.tags; + # TODO: tag settings + # Wait for this feature until option introspection for 'settings' is done. + # This might get too complex to handle otherwise. + # settingsViaTags = lib.filterAttrs ( + # tagName: _: machineHasTag machineName tagName + # ) instance.roles.${roleName}.tags; in { # TODO: Do we want to wrap settings with @@ -129,7 +132,7 @@ let settings = { imports = [ machineSettings - ] ++ lib.attrValues (lib.mapAttrs (_tagName: v: v.settings) settingsViaTags); + ]; # ++ lib.attrValues (lib.mapAttrs (_tagName: v: v.settings) settingsViaTags); }; } ); @@ -194,6 +197,10 @@ let ) { } evals; in { - inherit importedModuleWithInstances grouped; - inherit evals allMachines; + inherit + importedModuleWithInstances + grouped + evals + allMachines + ; } diff --git a/lib/distributed-service/tests/default.nix b/lib/distributed-service/tests/default.nix index 4ab86e351..7275f8ce3 100644 --- a/lib/distributed-service/tests/default.nix +++ b/lib/distributed-service/tests/default.nix @@ -258,70 +258,5 @@ in }; per_machine_args = import ./per_machine_args.nix { inherit lib callInventoryAdapter; }; - # test_per_machine_receives_instances = - # let - # res = callInventoryAdapter { - # # Authored module - # # A minimal module looks like this - # # It isn't exactly doing anything but it's a valid module that produces an output - # modules."A" = { - # _class = "clan.service"; - # manifest = { - # name = "network"; - # }; - # # Define a role without special behavior - # roles.peer = { }; - - # perMachine = - # { instances, ... }: - # { - # nixosModule = instances; - # }; - # }; - # machines = { - # jon = { }; - # sara = { }; - # }; - # instances."instance_foo" = { - # module = { - # name = "A"; - # }; - # roles.peer.machines.jon = { }; - # }; - # instances."instance_bar" = { - # module = { - # name = "A"; - # }; - # roles.peer.machines.sara = { }; - # }; - # instances."instance_zaza" = { - # module = { - # name = "B"; - # }; - # roles.peer.tags.all = { }; - # }; - # }; - # in - # { - # expr = { - # hasMachineSettings = - # res.evals.self-A.config.result.allMachines.jon.nixosModule. # { {instanceName} :: { roles :: { {roleName} :: { machines :: { {machineName} :: { settings :: {} } } } } } } - # instance_foo.roles.peer.machines.jon ? settings; - # machineSettingsEmpty = - # lib.filterAttrs (n: _v: n != "__functor" ) res.evals.self-A.config.result.allMachines.jon.nixosModule. # { {instanceName} :: { roles :: { {roleName} :: { machines :: { {machineName} :: { settings :: {} } } } } } } - # instance_foo.roles.peer.machines.jon.settings; - # hasRoleSettings = - # res.evals.self-A.config.result.allMachines.jon.nixosModule. # { {instanceName} :: { roles :: { {roleName} :: { machines :: { {machineName} :: { settings :: {} } } } } } } - # instance_foo.roles.peer ? settings; - # roleSettingsEmpty = - # lib.filterAttrs (n: _v: n != "__functor" ) res.evals.self-A.config.result.allMachines.jon.nixosModule. # { {instanceName} :: { roles :: { {roleName} :: { machines :: { {machineName} :: { settings :: {} } } } } } } - # instance_foo.roles.peer.settings; - # }; - # expected = { - # hasMachineSettings = true; - # machineSettingsEmpty = {}; - # hasRoleSettings = true; - # roleSettingsEmpty = {}; - # }; - # }; + per_instance_args = import ./per_instance_args.nix { inherit lib callInventoryAdapter; }; } diff --git a/lib/distributed-service/tests/per_instance_args.nix b/lib/distributed-service/tests/per_instance_args.nix new file mode 100644 index 000000000..88394e740 --- /dev/null +++ b/lib/distributed-service/tests/per_instance_args.nix @@ -0,0 +1,160 @@ +{ lib, callInventoryAdapter }: +let + # Authored module + # A minimal module looks like this + # It isn't exactly doing anything but it's a valid module that produces an output + modules."A" = { + _class = "clan.service"; + manifest = { + name = "network"; + }; + # Define two roles with unmergeable interfaces + # Both define some 'timeout' but with completely different types. + roles.peer.interface = + { lib, ... }: + { + options.timeout = lib.mkOption { + type = lib.types.str; + }; + }; + + roles.peer.perInstance = + { + instanceName, + settings, + machine, + ... + }: + let + settings1 = settings { + # Sometimes we want to create a default settings set depending on the machine config. + # Note: Other machines cannot depend on this settings. We must assign a new name to the settings. + # And thus the new value is not accessible by other machines. + timeout = lib.mkOverride 10 "config.blah"; + }; + in + { + nixosModule = { + inherit instanceName settings machine; + + # We are double vendoring the settings + # To test that we can do it indefinitely + vendoredSettings = settings1 { + # Sometimes we want to create a default settings set depending on the machine config. + # Note: Other machines cannot depend on this settings. We must assign a new name to the settings. + # And thus the new value is not accessible by other machines. + timeout = lib.mkOverride 5 "config.thing"; + }; + }; + }; + }; + machines = { + jon = { }; + sara = { }; + }; + res = callInventoryAdapter { + inherit modules machines; + instances."instance_foo" = { + module = { + name = "A"; + }; + roles.peer.machines.jon = { + settings.timeout = lib.mkForce "foo-peer-jon"; + }; + roles.peer = { + settings.timeout = "foo-peer"; + }; + }; + instances."instance_bar" = { + module = { + name = "A"; + }; + roles.peer.machines.jon = { + settings.timeout = "bar-peer-jon"; + }; + }; + # import the module "B" (undefined) + # All machines have this instance + instances."instance_zaza" = { + module = { + name = "B"; + }; + roles.peer.tags.all = { }; + }; + }; + + filterInternals = lib.filterAttrs (n: _v: !lib.hasPrefix "_" n); + + # Replace internal attributes ('_' prefix) + # So we catch their presence but don't check the value + mapInternalsRecursive = lib.mapAttrsRecursive ( + path: v: + let + name = lib.last path; + in + if !lib.hasPrefix "_" name then v else name + ); +in +{ + # settings should evaluate + test_per_instance_arguments = { + expr = { + instanceName = + res.evals.self-A.config.result.allRoles.peer.allInstances."instance_foo".allMachines.jon.nixosModule.instanceName; + + # settings are specific. + # Below we access: + # instance = instance_foo + # roles = peer + # machines = jon + settings = filterInternals res.evals.self-A.config.result.allRoles.peer.allInstances.instance_foo.allMachines.jon.nixosModule.settings; + machine = mapInternalsRecursive res.evals.self-A.config.result.allRoles.peer.allInstances.instance_foo.allMachines.jon.nixosModule.machine; + + # hasRoleSettings = + # res.evals.self-A.config.result.allMachines.jon.nixosModule.instance_foo.roles.peer ? settings; + + # # settings are specific. + # # Below we access: + # # instance = instance_foo + # # roles = peer + # # machines = * + # specificRoleSettings = filterInternals res.evals.self-A.config.result.allMachines.jon.nixosModule.instance_foo.roles.peer.settings; + }; + expected = { + instanceName = "instance_foo"; + settings = { + timeout = "foo-peer-jon"; + }; + machine = { + name = "jon"; + roles = { + peer = { + machines = { + jon = { + settings = { + __functor = "__functor"; + timeout = "foo-peer-jon"; + }; + }; + }; + settings = { + __functor = "__functor"; + timeout = "foo-peer"; + }; + }; + }; + }; + }; + }; + + test_per_instance_settings_vendoring = { + expr = + mapInternalsRecursive + res.evals.self-A.config.result.allRoles.peer.allInstances."instance_foo".allMachines.jon.nixosModule.vendoredSettings; + expected = { + # Returns another override + __functor = "__functor"; + timeout = "config.thing"; + }; + }; +} diff --git a/lib/distributed-service/tests/per_machine_args.nix b/lib/distributed-service/tests/per_machine_args.nix index ad4784af6..2f5868e3e 100644 --- a/lib/distributed-service/tests/per_machine_args.nix +++ b/lib/distributed-service/tests/per_machine_args.nix @@ -1,6 +1,6 @@ { lib, callInventoryAdapter }: - -let # Authored module +let + # Authored module # A minimal module looks like this # It isn't exactly doing anything but it's a valid module that produces an output modules."A" = {