From 84cd0d03109354f6f207a7794cf55bf56aba506a Mon Sep 17 00:00:00 2001 From: Johannes Kirschbauer Date: Tue, 1 Apr 2025 12:49:33 +0200 Subject: [PATCH] test(inventory/instnces): add tests for 'perInstance' arguments --- lib/distributed-service/inventory-adapter.nix | 7 +- lib/distributed-service/tests/default.nix | 67 +------- .../tests/per_instance_args.nix | 160 ++++++++++++++++++ .../tests/per_machine_args.nix | 4 +- 4 files changed, 169 insertions(+), 69 deletions(-) create mode 100644 lib/distributed-service/tests/per_instance_args.nix diff --git a/lib/distributed-service/inventory-adapter.nix b/lib/distributed-service/inventory-adapter.nix index ad46957c8..d99c1bf74 100644 --- a/lib/distributed-service/inventory-adapter.nix +++ b/lib/distributed-service/inventory-adapter.nix @@ -197,5 +197,10 @@ let ) { } evals; in { - inherit importedModuleWithInstances grouped 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" = {