Warnings and Assertions
When configuration problems are detectable in a module, it is a good idea to
write an assertion or warning. Doing so provides clear feedback to the user
and can prevent errors before the build.
Although Nix has the abort and
builtins.trace
functions
to perform such tasks, they are not ideally suited for NixOS modules. Instead
of these functions, you can declare your warnings and assertions using the
NixOS module system.
Defining Warnings and Assertions
Both warnings and assertions can be defined using the option. Each assertion needs an attribute name, under which you have to define an enable condition using and a message using . Note that the enable condition is inverse of what an assertion would be: To assert a value being true, the enable condition should be false in that case, so that it isn't triggered. For the assertion message, you can add options to the module arguments and use ${options.path.to.option} to print a context-aware string representation of the option path. Here is an example showing how this can be done.
{ config, options, ... }: {
_module.assertions.gpgSshAgent = {
enable = config.programs.gnupg.agent.enableSSHSupport && config.programs.ssh.startAgent;
message = "You can't enable both ${options.programs.ssh.startAgent}"
+ " and ${options.programs.gnupg.agent.enableSSHSupport}!";
};
_module.assertions.grafanaPassword = {
enable = config.services.grafana.database.password != "";
message = "The grafana password defined with ${options.services.grafana.database.password}"
+ " will be stored as plaintext in the Nix store!";
# This is a non-fatal warning
type = "warning";
};
}
Ignoring Warnings and Assertions
Sometimes you can get warnings or assertions that don't apply to your specific case and you wish to ignore them, or at least make assertions non-fatal. You can do so for all assertions defined using by using the attribute name of the definition, which is conveniently printed using [...] when the assertion is triggered. For above example, the evaluation output when the assertions are triggered looks as follows:
trace: warning: [grafanaPassword] The grafana password defined with
services.grafana.database.password will be stored as plaintext in the Nix store!
error: Failed assertions:
- [gpgSshAgent] You can't enable both programs.ssh.startAgent and
programs.gnupg.agent.enableSSHSupport!
The [grafanaPassword] and [gpgSshAgent] strings tell you that these were defined under the grafanaPassword and gpgSshAgent attributes of respectively. With this knowledge you can adjust them to your liking:
{ lib, ... }: {
# Change the assertion into a non-fatal warning
_module.assertions.gpgSshAgent.type = "warning";
# We don't care about this warning, disable it
_module.assertions.grafanaPassword.enable = lib.mkForce false;
}
Warnings and Assertions in Submodules
Warnings and assertions can be defined within submodules in the same way. Here is an example:
{ lib, ... }: {
options.myServices = lib.mkOption {
type = lib.types.attrsOf (lib.types.submodule ({ config, options, ... }: {
options.port = lib.mkOption {};
config._module.assertions.portConflict = {
enable = config.port == 80;
message = "Port ${toString config.port} defined using"
+ " ${options.port} is usually used for HTTP";
type = "warning";
};
}));
};
}
When this assertion is triggered, it shows both the submodule path along with the assertion attribute within that submodule, joined by a /. Note also how ${options.port} correctly shows the context of the option.
trace: warning: [myServices.foo/portConflict] Port 80 defined using
myServices.foo.port is usually used for HTTP
Therefore to disable such an assertion, you can do so by changing the option within the myServices.foo submodule:
{ lib, ... }: {
myServices.foo._module.assertions.portConflict.enable = lib.mkForce false;
}
Assertions defined in submodules under types.listOf can't be ignored, since there's no way to change previously defined list items.