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:
parent
991dfccbd1
commit
767d80099c
13 changed files with 56 additions and 48 deletions
|
@ -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}'.";
|
||||
};
|
||||
|
|
|
@ -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
|
||||
|
|
8
lib/tests/modules/assertions/condition-true.nix
Normal file
8
lib/tests/modules/assertions/condition-true.nix
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
|
||||
_module.checks.test = {
|
||||
check = true;
|
||||
message = "Assertion failed";
|
||||
};
|
||||
|
||||
}
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
_module.checks.test = {
|
||||
enable = false;
|
||||
check = false;
|
||||
message = "Assertion failed";
|
||||
};
|
||||
|
||||
|
|
|
@ -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";
|
||||
};
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
_module.checks.test = {
|
||||
enable = true;
|
||||
check = false;
|
||||
message = "Assertion failed";
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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";
|
||||
};
|
||||
}));
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
default = { bar = {}; };
|
||||
type = lib.types.attrsOf (lib.types.submodule {
|
||||
_module.checks.test = {
|
||||
enable = true;
|
||||
check = false;
|
||||
message = "Assertion failed";
|
||||
};
|
||||
});
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
default = {};
|
||||
type = lib.types.submodule {
|
||||
_module.checks.test = {
|
||||
enable = true;
|
||||
check = false;
|
||||
message = "Assertion failed";
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
|
||||
_module.checks._test = {
|
||||
enable = true;
|
||||
check = false;
|
||||
message = "Assertion failed";
|
||||
};
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
|
||||
_module.checks.test = {
|
||||
enable = true;
|
||||
check = false;
|
||||
type = "warning";
|
||||
message = "Warning message";
|
||||
};
|
||||
|
|
|
@ -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 && 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>
|
||||
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue