lib/modules: Introduce _module.checks.*.check

Previously the .enable option was used to encode the condition as well,
which lead to some oddness:
- In order to encode an assertion, one had to invert it
- To disable a check, one had to mkForce it

By introducing a separate .check option this is solved because:
- It can be used to encode assertions
- Disabling is done separately with .enable option, whose default can be
  overridden without a mkForce
This commit is contained in:
Silvan Mosberger 2020-11-30 22:38:56 +01:00
parent 991dfccbd1
commit 767d80099c
No known key found for this signature in database
GPG Key ID: E8F1E9EAD284E17D
13 changed files with 56 additions and 48 deletions

View File

@ -155,17 +155,22 @@ rec {
default = {};
internal = prefix != [];
type = types.attrsOf (types.submodule {
# TODO: Rename to assertion? Or allow also setting assertion?
options.enable = mkOption {
description = ''
Whether to enable this check.
<note><para>
This is the inverse of asserting a condition: If a certain
condition should be <literal>true</literal>, then this
option should be set to <literal>false</literal> when that
case occurs
</para></note>
Whether to enable this check. Set this to false to not trigger
any errors or warning messages. This is useful for ignoring a
check in case it doesn't make sense in certain scenarios.
'';
default = true;
type = types.bool;
};
options.check = mkOption {
description = ''
The condition that must succeed in order for this check to be
successful and not trigger a warning or error.
'';
readOnly = true;
type = types.bool;
};
@ -189,9 +194,7 @@ rec {
and use <literal>''${options.path.to.option}</literal>.
'';
type = types.str;
example = literalExample ''
Enabling both ''${options.services.foo.enable} and ''${options.services.bar.enable} is not possible.
'';
example = "Enabling both \${options.services.foo.enable} and \${options.services.bar.enable} is not possible.";
};
});
};
@ -244,7 +247,7 @@ rec {
if lib.hasPrefix "_" name then value.message
else "[${showOption prefix}${optionalString (prefix != []) "/"}${name}] ${value.message}";
in
if ! value.enable then errors
if value.enable -> value.check then errors
else if value.type == "warning" then lib.warn show errors
else if value.type == "error" then errors ++ [ show ]
else abort "Unknown check type ${value.type}";
@ -885,8 +888,7 @@ rec {
});
config._module.checks =
let opt = getAttrFromPath optionName options; in {
${showOption optionName} = {
enable = mkDefault opt.isDefined;
${showOption optionName} = lib.mkIf opt.isDefined {
message = ''
The option definition `${showOption optionName}' in ${showFiles opt.files} no longer has any effect; please remove it.
${replacementInstructions}
@ -958,8 +960,7 @@ rec {
let val = getAttrFromPath f config;
opt = getAttrFromPath f options;
in {
${showOption f} = {
enable = mkDefault (val != "_mkMergedOptionModule");
${showOption f} = lib.mkIf (val != "_mkMergedOptionModule") {
type = "warning";
message = "The option `${showOption f}' defined in ${showFiles opt.files} has been changed to `${showOption to}' that has a different type. Please read `${showOption to}' documentation and update your configuration accordingly.";
};
@ -1024,8 +1025,7 @@ rec {
});
config = mkMerge [
{
_module.checks.${showOption from} = {
enable = mkDefault (warn && fromOpt.isDefined);
_module.checks.${showOption from} = mkIf (warn && fromOpt.isDefined) {
type = "warning";
message = "The option `${showOption from}' defined in ${showFiles fromOpt.files} has been renamed to `${showOption to}'.";
};

View File

@ -279,7 +279,8 @@ checkConfigOutput baz config.value.nested.bar.baz ./types-anything/mk-mods.nix
# Check that assertions are triggered by default for just evaluating config
checkConfigError 'Failed checks:\n- \[test\] Assertion failed' config ./assertions/simple.nix
# Assertion is not triggered when enable is false
# Assertion is not triggered when enable is false or condition is true
checkConfigOutput '{ }' config ./assertions/condition-true.nix
checkConfigOutput '{ }' config ./assertions/enable-false.nix
# Warnings should be displayed on standard error

View File

@ -0,0 +1,8 @@
{
_module.checks.test = {
check = true;
message = "Assertion failed";
};
}

View File

@ -2,6 +2,7 @@
_module.checks.test = {
enable = false;
check = false;
message = "Assertion failed";
};

View File

@ -2,20 +2,20 @@
_module.checks = {
test1 = {
enable = true;
check = false;
message = "Assertion 1 failed";
};
test2 = {
enable = true;
check = false;
message = "Assertion 2 failed";
};
test3 = {
enable = true;
check = false;
message = "Warning 3 failed";
type = "warning";
};
test4 = {
enable = true;
check = false;
message = "Warning 4 failed";
type = "warning";
};

View File

@ -1,6 +1,6 @@
{
_module.checks.test = {
enable = true;
check = false;
message = "Assertion failed";
};
}

View File

@ -4,7 +4,7 @@
default = { bar.baz = {}; };
type = lib.types.attrsOf (lib.types.attrsOf (lib.types.submodule {
_module.checks.test = {
enable = true;
check = false;
message = "Assertion failed";
};
}));

View File

@ -4,7 +4,7 @@
default = { bar = {}; };
type = lib.types.attrsOf (lib.types.submodule {
_module.checks.test = {
enable = true;
check = false;
message = "Assertion failed";
};
});

View File

@ -4,7 +4,7 @@
default = {};
type = lib.types.submodule {
_module.checks.test = {
enable = true;
check = false;
message = "Assertion failed";
};
};

View File

@ -1,7 +1,7 @@
{
_module.checks._test = {
enable = true;
check = false;
message = "Assertion failed";
};

View File

@ -1,7 +1,7 @@
{
_module.checks.test = {
enable = true;
check = false;
type = "warning";
message = "Warning message";
};

View File

@ -25,28 +25,26 @@
<para>
Checks can be defined using the <xref linkend="opt-_module.checks"/> option.
Each check needs an attribute name, under which you have to define an enable
condition using <xref linkend="opt-_module.checks._name_.enable"/> and a
message using <xref linkend="opt-_module.checks._name_.message"/>. Note that
the enable condition is <emphasis>inverse</emphasis> 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 check message, you can add
Each check needs an attribute name, under which you can define a trigger
assertion using <xref linkend="opt-_module.checks._name_.check"/> and a
message using <xref linkend="opt-_module.checks._name_.message"/>.
For the message, you can add
<literal>options</literal> to the module arguments and use
<literal>${options.path.to.option}</literal> to print a context-aware string
representation of the option path. Here is an example showing how this can be
representation of an option path. Here is an example showing how this can be
done.
</para>
<programlisting>
{ config, options, ... }: {
_module.checks.gpgSshAgent = {
enable = config.programs.gnupg.agent.enableSSHSupport &amp;&amp; config.programs.ssh.startAgent;
message = "You can't enable both ${options.programs.ssh.startAgent}"
+ " and ${options.programs.gnupg.agent.enableSSHSupport}!";
check = config.programs.gnupg.agent.enableSSHSupport -> !config.programs.ssh.startAgent;
message = "If you have ${options.programs.gnupg.agent.enableSSHSupport} enabled,"
+ " you can't enable ${options.programs.ssh.startAgent} as well!";
};
_module.checks.grafanaPassword = {
enable = config.services.grafana.database.password != "";
check = 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
@ -74,8 +72,8 @@
trace: warning: [grafanaPassword] The grafana password defined with
services.grafana.database.password will be stored as plaintext in the Nix store!
error: Failed checks:
- [gpgSshAgent] You can't enable both programs.ssh.startAgent and
programs.gnupg.agent.enableSSHSupport!
- [gpgSshAgent] If you have programs.gnupg.agent.enableSSHSupport
enabled, you can't enable programs.ssh.startAgent as well!
</programlisting>
<para>
@ -87,12 +85,12 @@ error: Failed checks:
</para>
<programlisting>
{ lib, ... }: {
{
# Change the error into a non-fatal warning
_module.checks.gpgSshAgent.type = "warning";
# We don't care about this warning, disable it
_module.checks.grafanaPassword.enable = lib.mkForce false;
_module.checks.grafanaPassword.enable = false;
}
</programlisting>
@ -113,7 +111,7 @@ error: Failed checks:
options.port = lib.mkOption {};
config._module.checks.portConflict = {
enable = config.port == 80;
check = config.port != 80;
message = "Port ${toString config.port} defined using"
+ " ${options.port} is usually used for HTTP";
type = "warning";
@ -143,8 +141,8 @@ trace: warning: [myServices.foo/portConflict] Port 80 defined using
</para>
<programlisting>
{ lib, ... }: {
myServices.foo._module.checks.portConflict.enable = lib.mkForce false;
{
myServices.foo._module.checks.portConflict.enable = false;
}
</programlisting>

View File

@ -36,7 +36,7 @@ with lib;
name = "_${toString n}";
isWarning = lib.isString value;
result = {
enable = if isWarning then true else ! value.assertion;
check = if isWarning then false else value.assertion;
type = if isWarning then "warning" else "error";
message = if isWarning then value else value.message;
};