I believe a custom require feature was chosen in order to solve a bootstrapping problem.
This custom mechanism is not ideal because it adds to the growing list of "override", "overlay", "module", etc. It'd be better to reuse the module system. I suggest the following bootstrapping
Load the raw network expressions as Nix functions
Call them with bogus auto-arguments as in module (mapAttrs (k: v: throw k) (functionArgs module)
This should give us module attrsets containing a few attributes of interest that we'll evaluated and a bunch of things we don't need and can't evaluate at this stage. That's ok.
Use these partial modules to determine what lib should be.
Call lib.evalModules on the network expressions.
Have the module system at our disposal
A more radical approach, suitable for flakes, is to let the user inject lib manually. This would change the flake format to require a call like nixopsConfigurations.default = inputs.nixops.lib.mkNetwork { inherit lib; configuration = /* ... */; }, which is more robust than step 3 and 4. For non-flake nixops.nix we'll still want something like step 3 and 4 I think.
Pros:
module system is more familiar
module system is more powerful a custom composition method will be
allows the user to define network-wide options to capture the variability of environments (dtap, etc)
allows the user to define network-wide options to represent cross-machine concepts such as groups of machines
step 3 and 4 are somewhat fragile. I am confident that this can be mitigated
Myths:
"the module system is slow; look at NixOS": the module system scales with the number of options. It is very fast for small configuration systems. It is also quite lazy, so it is possible to avoid evaluating the machines for example. I see no reason for performance to degrade because of this change.
Note to self: requires is not a recent addition; compatibility is needed and can be provided at next to no cost, because the module system still supports requires as a hardly known legacy feature.
I believe a custom
require
feature was chosen in order to solve a bootstrapping problem. This custom mechanism is not ideal because it adds to the growing list of "override", "overlay", "module", etc. It'd be better to reuse the module system. I suggest the following bootstrappingmodule (mapAttrs (k: v: throw k) (functionArgs module)
lib
should be.lib.evalModules
on the network expressions.A more radical approach, suitable for flakes, is to let the user inject
lib
manually. This would change the flake format to require a call likenixopsConfigurations.default = inputs.nixops.lib.mkNetwork { inherit lib; configuration = /* ... */; }
, which is more robust than step 3 and 4. For non-flakenixops.nix
we'll still want something like step 3 and 4 I think.Pros:
Cons:
Myths:
Note to self:
requires
is not a recent addition; compatibility is needed and can be provided at next to no cost, because the module system still supportsrequires
as a hardly known legacy feature.