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 = {};
|
default = {};
|
||||||
internal = prefix != [];
|
internal = prefix != [];
|
||||||
type = types.attrsOf (types.submodule {
|
type = types.attrsOf (types.submodule {
|
||||||
# TODO: Rename to assertion? Or allow also setting assertion?
|
|
||||||
options.enable = mkOption {
|
options.enable = mkOption {
|
||||||
description = ''
|
description = ''
|
||||||
Whether to enable this check.
|
Whether to enable this check. Set this to false to not trigger
|
||||||
<note><para>
|
any errors or warning messages. This is useful for ignoring a
|
||||||
This is the inverse of asserting a condition: If a certain
|
check in case it doesn't make sense in certain scenarios.
|
||||||
condition should be <literal>true</literal>, then this
|
|
||||||
option should be set to <literal>false</literal> when that
|
|
||||||
case occurs
|
|
||||||
</para></note>
|
|
||||||
'';
|
'';
|
||||||
|
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;
|
type = types.bool;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -189,9 +194,7 @@ rec {
|
||||||
and use <literal>''${options.path.to.option}</literal>.
|
and use <literal>''${options.path.to.option}</literal>.
|
||||||
'';
|
'';
|
||||||
type = types.str;
|
type = types.str;
|
||||||
example = literalExample ''
|
example = "Enabling both \${options.services.foo.enable} and \${options.services.bar.enable} is not possible.";
|
||||||
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
|
if lib.hasPrefix "_" name then value.message
|
||||||
else "[${showOption prefix}${optionalString (prefix != []) "/"}${name}] ${value.message}";
|
else "[${showOption prefix}${optionalString (prefix != []) "/"}${name}] ${value.message}";
|
||||||
in
|
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 == "warning" then lib.warn show errors
|
||||||
else if value.type == "error" then errors ++ [ show ]
|
else if value.type == "error" then errors ++ [ show ]
|
||||||
else abort "Unknown check type ${value.type}";
|
else abort "Unknown check type ${value.type}";
|
||||||
|
@ -885,8 +888,7 @@ rec {
|
||||||
});
|
});
|
||||||
config._module.checks =
|
config._module.checks =
|
||||||
let opt = getAttrFromPath optionName options; in {
|
let opt = getAttrFromPath optionName options; in {
|
||||||
${showOption optionName} = {
|
${showOption optionName} = lib.mkIf opt.isDefined {
|
||||||
enable = mkDefault opt.isDefined;
|
|
||||||
message = ''
|
message = ''
|
||||||
The option definition `${showOption optionName}' in ${showFiles opt.files} no longer has any effect; please remove it.
|
The option definition `${showOption optionName}' in ${showFiles opt.files} no longer has any effect; please remove it.
|
||||||
${replacementInstructions}
|
${replacementInstructions}
|
||||||
|
@ -958,8 +960,7 @@ rec {
|
||||||
let val = getAttrFromPath f config;
|
let val = getAttrFromPath f config;
|
||||||
opt = getAttrFromPath f options;
|
opt = getAttrFromPath f options;
|
||||||
in {
|
in {
|
||||||
${showOption f} = {
|
${showOption f} = lib.mkIf (val != "_mkMergedOptionModule") {
|
||||||
enable = mkDefault (val != "_mkMergedOptionModule");
|
|
||||||
type = "warning";
|
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.";
|
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 [
|
config = mkMerge [
|
||||||
{
|
{
|
||||||
_module.checks.${showOption from} = {
|
_module.checks.${showOption from} = mkIf (warn && fromOpt.isDefined) {
|
||||||
enable = mkDefault (warn && fromOpt.isDefined);
|
|
||||||
type = "warning";
|
type = "warning";
|
||||||
message = "The option `${showOption from}' defined in ${showFiles fromOpt.files} has been renamed to `${showOption to}'.";
|
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
|
# Check that assertions are triggered by default for just evaluating config
|
||||||
checkConfigError 'Failed checks:\n- \[test\] Assertion failed' config ./assertions/simple.nix
|
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
|
checkConfigOutput '{ }' config ./assertions/enable-false.nix
|
||||||
|
|
||||||
# Warnings should be displayed on standard error
|
# 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 = {
|
_module.checks.test = {
|
||||||
enable = false;
|
enable = false;
|
||||||
|
check = false;
|
||||||
message = "Assertion failed";
|
message = "Assertion failed";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -2,20 +2,20 @@
|
||||||
|
|
||||||
_module.checks = {
|
_module.checks = {
|
||||||
test1 = {
|
test1 = {
|
||||||
enable = true;
|
check = false;
|
||||||
message = "Assertion 1 failed";
|
message = "Assertion 1 failed";
|
||||||
};
|
};
|
||||||
test2 = {
|
test2 = {
|
||||||
enable = true;
|
check = false;
|
||||||
message = "Assertion 2 failed";
|
message = "Assertion 2 failed";
|
||||||
};
|
};
|
||||||
test3 = {
|
test3 = {
|
||||||
enable = true;
|
check = false;
|
||||||
message = "Warning 3 failed";
|
message = "Warning 3 failed";
|
||||||
type = "warning";
|
type = "warning";
|
||||||
};
|
};
|
||||||
test4 = {
|
test4 = {
|
||||||
enable = true;
|
check = false;
|
||||||
message = "Warning 4 failed";
|
message = "Warning 4 failed";
|
||||||
type = "warning";
|
type = "warning";
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
_module.checks.test = {
|
_module.checks.test = {
|
||||||
enable = true;
|
check = false;
|
||||||
message = "Assertion failed";
|
message = "Assertion failed";
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
default = { bar.baz = {}; };
|
default = { bar.baz = {}; };
|
||||||
type = lib.types.attrsOf (lib.types.attrsOf (lib.types.submodule {
|
type = lib.types.attrsOf (lib.types.attrsOf (lib.types.submodule {
|
||||||
_module.checks.test = {
|
_module.checks.test = {
|
||||||
enable = true;
|
check = false;
|
||||||
message = "Assertion failed";
|
message = "Assertion failed";
|
||||||
};
|
};
|
||||||
}));
|
}));
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
default = { bar = {}; };
|
default = { bar = {}; };
|
||||||
type = lib.types.attrsOf (lib.types.submodule {
|
type = lib.types.attrsOf (lib.types.submodule {
|
||||||
_module.checks.test = {
|
_module.checks.test = {
|
||||||
enable = true;
|
check = false;
|
||||||
message = "Assertion failed";
|
message = "Assertion failed";
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
default = {};
|
default = {};
|
||||||
type = lib.types.submodule {
|
type = lib.types.submodule {
|
||||||
_module.checks.test = {
|
_module.checks.test = {
|
||||||
enable = true;
|
check = false;
|
||||||
message = "Assertion failed";
|
message = "Assertion failed";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
|
|
||||||
_module.checks._test = {
|
_module.checks._test = {
|
||||||
enable = true;
|
check = false;
|
||||||
message = "Assertion failed";
|
message = "Assertion failed";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
|
|
||||||
_module.checks.test = {
|
_module.checks.test = {
|
||||||
enable = true;
|
check = false;
|
||||||
type = "warning";
|
type = "warning";
|
||||||
message = "Warning message";
|
message = "Warning message";
|
||||||
};
|
};
|
||||||
|
|
|
@ -25,28 +25,26 @@
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
Checks can be defined using the <xref linkend="opt-_module.checks"/> option.
|
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
|
Each check needs an attribute name, under which you can define a trigger
|
||||||
condition using <xref linkend="opt-_module.checks._name_.enable"/> and a
|
assertion using <xref linkend="opt-_module.checks._name_.check"/> and a
|
||||||
message using <xref linkend="opt-_module.checks._name_.message"/>. Note that
|
message using <xref linkend="opt-_module.checks._name_.message"/>.
|
||||||
the enable condition is <emphasis>inverse</emphasis> of what an assertion
|
For the message, you can add
|
||||||
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
|
|
||||||
<literal>options</literal> to the module arguments and use
|
<literal>options</literal> to the module arguments and use
|
||||||
<literal>${options.path.to.option}</literal> to print a context-aware string
|
<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.
|
done.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
<programlisting>
|
<programlisting>
|
||||||
{ config, options, ... }: {
|
{ config, options, ... }: {
|
||||||
_module.checks.gpgSshAgent = {
|
_module.checks.gpgSshAgent = {
|
||||||
enable = config.programs.gnupg.agent.enableSSHSupport && config.programs.ssh.startAgent;
|
check = config.programs.gnupg.agent.enableSSHSupport -> !config.programs.ssh.startAgent;
|
||||||
message = "You can't enable both ${options.programs.ssh.startAgent}"
|
message = "If you have ${options.programs.gnupg.agent.enableSSHSupport} enabled,"
|
||||||
+ " and ${options.programs.gnupg.agent.enableSSHSupport}!";
|
+ " you can't enable ${options.programs.ssh.startAgent} as well!";
|
||||||
};
|
};
|
||||||
|
|
||||||
_module.checks.grafanaPassword = {
|
_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}"
|
message = "The grafana password defined with ${options.services.grafana.database.password}"
|
||||||
+ " will be stored as plaintext in the Nix store!";
|
+ " will be stored as plaintext in the Nix store!";
|
||||||
# This is a non-fatal warning
|
# This is a non-fatal warning
|
||||||
|
@ -74,8 +72,8 @@
|
||||||
trace: warning: [grafanaPassword] The grafana password defined with
|
trace: warning: [grafanaPassword] The grafana password defined with
|
||||||
services.grafana.database.password will be stored as plaintext in the Nix store!
|
services.grafana.database.password will be stored as plaintext in the Nix store!
|
||||||
error: Failed checks:
|
error: Failed checks:
|
||||||
- [gpgSshAgent] You can't enable both programs.ssh.startAgent and
|
- [gpgSshAgent] If you have programs.gnupg.agent.enableSSHSupport
|
||||||
programs.gnupg.agent.enableSSHSupport!
|
enabled, you can't enable programs.ssh.startAgent as well!
|
||||||
</programlisting>
|
</programlisting>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
|
@ -87,12 +85,12 @@ error: Failed checks:
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
<programlisting>
|
<programlisting>
|
||||||
{ lib, ... }: {
|
{
|
||||||
# Change the error into a non-fatal warning
|
# Change the error into a non-fatal warning
|
||||||
_module.checks.gpgSshAgent.type = "warning";
|
_module.checks.gpgSshAgent.type = "warning";
|
||||||
|
|
||||||
# We don't care about this warning, disable it
|
# We don't care about this warning, disable it
|
||||||
_module.checks.grafanaPassword.enable = lib.mkForce false;
|
_module.checks.grafanaPassword.enable = false;
|
||||||
}
|
}
|
||||||
</programlisting>
|
</programlisting>
|
||||||
|
|
||||||
|
@ -113,7 +111,7 @@ error: Failed checks:
|
||||||
options.port = lib.mkOption {};
|
options.port = lib.mkOption {};
|
||||||
|
|
||||||
config._module.checks.portConflict = {
|
config._module.checks.portConflict = {
|
||||||
enable = config.port == 80;
|
check = config.port != 80;
|
||||||
message = "Port ${toString config.port} defined using"
|
message = "Port ${toString config.port} defined using"
|
||||||
+ " ${options.port} is usually used for HTTP";
|
+ " ${options.port} is usually used for HTTP";
|
||||||
type = "warning";
|
type = "warning";
|
||||||
|
@ -143,8 +141,8 @@ trace: warning: [myServices.foo/portConflict] Port 80 defined using
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
<programlisting>
|
<programlisting>
|
||||||
{ lib, ... }: {
|
{
|
||||||
myServices.foo._module.checks.portConflict.enable = lib.mkForce false;
|
myServices.foo._module.checks.portConflict.enable = false;
|
||||||
}
|
}
|
||||||
</programlisting>
|
</programlisting>
|
||||||
|
|
||||||
|
|
|
@ -36,7 +36,7 @@ with lib;
|
||||||
name = "_${toString n}";
|
name = "_${toString n}";
|
||||||
isWarning = lib.isString value;
|
isWarning = lib.isString value;
|
||||||
result = {
|
result = {
|
||||||
enable = if isWarning then true else ! value.assertion;
|
check = if isWarning then false else value.assertion;
|
||||||
type = if isWarning then "warning" else "error";
|
type = if isWarning then "warning" else "error";
|
||||||
message = if isWarning then value else value.message;
|
message = if isWarning then value else value.message;
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue