Revert "Module-builtin assertions, disabling assertions and submodule assertions"
This commit is contained in:
parent
fd1cc29974
commit
9e6737710c
164
lib/modules.nix
164
lib/modules.nix
|
@ -46,7 +46,6 @@ let
|
||||||
showFiles
|
showFiles
|
||||||
showOption
|
showOption
|
||||||
unknownModule
|
unknownModule
|
||||||
literalExample
|
|
||||||
;
|
;
|
||||||
in
|
in
|
||||||
|
|
||||||
|
@ -73,20 +72,14 @@ rec {
|
||||||
check ? true
|
check ? true
|
||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
# An internal module that's always added, defining special options which
|
# This internal module declare internal options under the `_module'
|
||||||
# change the behavior of the module evaluation itself. This is under a
|
# attribute. These options are fragile, as they are used by the
|
||||||
# `_`-prefixed namespace in order to prevent name clashes with
|
# module system to change the interpretation of modules.
|
||||||
# user-defined options
|
|
||||||
internalModule = rec {
|
internalModule = rec {
|
||||||
# FIXME: Using ./modules.nix directly breaks the doc for some reason
|
_file = ./modules.nix;
|
||||||
_file = "lib/modules.nix";
|
|
||||||
|
|
||||||
key = _file;
|
key = _file;
|
||||||
|
|
||||||
# Most of these options are set to be internal only for prefix != [],
|
|
||||||
# aka it's a submodule evaluation. This way their docs are displayed
|
|
||||||
# only once as a top-level NixOS option, but will be hidden for all
|
|
||||||
# submodules, even though they are available there too
|
|
||||||
options = {
|
options = {
|
||||||
_module.args = mkOption {
|
_module.args = mkOption {
|
||||||
# Because things like `mkIf` are entirely useless for
|
# Because things like `mkIf` are entirely useless for
|
||||||
|
@ -96,7 +89,7 @@ rec {
|
||||||
# a `_module.args.pkgs = import (fetchTarball { ... }) {}` won't
|
# a `_module.args.pkgs = import (fetchTarball { ... }) {}` won't
|
||||||
# start a download when `pkgs` wasn't evaluated.
|
# start a download when `pkgs` wasn't evaluated.
|
||||||
type = types.lazyAttrsOf types.unspecified;
|
type = types.lazyAttrsOf types.unspecified;
|
||||||
internal = prefix != [];
|
internal = true;
|
||||||
description = "Arguments passed to each module.";
|
description = "Arguments passed to each module.";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -104,19 +97,13 @@ rec {
|
||||||
type = types.bool;
|
type = types.bool;
|
||||||
internal = true;
|
internal = true;
|
||||||
default = check;
|
default = check;
|
||||||
description = ''
|
description = "Whether to check whether all option definitions have matching declarations.";
|
||||||
Whether to check whether all option definitions have matching
|
|
||||||
declarations.
|
|
||||||
|
|
||||||
Note that this has nothing to do with the similarly named
|
|
||||||
<option>_module.checks</option> option
|
|
||||||
'';
|
|
||||||
};
|
};
|
||||||
|
|
||||||
_module.freeformType = mkOption {
|
_module.freeformType = mkOption {
|
||||||
# Disallow merging for now, but could be implemented nicely with a `types.optionType`
|
# Disallow merging for now, but could be implemented nicely with a `types.optionType`
|
||||||
type = types.nullOr (types.uniq types.attrs);
|
type = types.nullOr (types.uniq types.attrs);
|
||||||
internal = prefix != [];
|
internal = true;
|
||||||
default = null;
|
default = null;
|
||||||
description = ''
|
description = ''
|
||||||
If set, merge all definitions that don't have an associated option
|
If set, merge all definitions that don't have an associated option
|
||||||
|
@ -129,75 +116,6 @@ rec {
|
||||||
turned off.
|
turned off.
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
_module.checks = mkOption {
|
|
||||||
description = ''
|
|
||||||
Evaluation checks to trigger during module evaluation. The
|
|
||||||
attribute name will be displayed when it is triggered, allowing
|
|
||||||
users to disable/change these checks if necessary. See
|
|
||||||
the section on Warnings and Assertions in the manual for more
|
|
||||||
information.
|
|
||||||
'';
|
|
||||||
example = literalExample ''
|
|
||||||
{
|
|
||||||
gpgSshAgent = {
|
|
||||||
enable = config.programs.gnupg.agent.enableSSHSupport && config.programs.ssh.startAgent;
|
|
||||||
message = "You can't use ssh-agent and GnuPG agent with SSH support enabled at the same time!";
|
|
||||||
};
|
|
||||||
|
|
||||||
grafanaPassword = {
|
|
||||||
enable = config.services.grafana.database.password != "";
|
|
||||||
message = "Grafana passwords will be stored as plaintext in the Nix store!";
|
|
||||||
type = "warning";
|
|
||||||
};
|
|
||||||
}
|
|
||||||
'';
|
|
||||||
default = {};
|
|
||||||
internal = prefix != [];
|
|
||||||
type = types.attrsOf (types.submodule {
|
|
||||||
options.enable = mkOption {
|
|
||||||
description = ''
|
|
||||||
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;
|
|
||||||
};
|
|
||||||
|
|
||||||
options.type = mkOption {
|
|
||||||
description = ''
|
|
||||||
The type of the check. The default
|
|
||||||
<literal>"error"</literal> type will cause evaluation to fail,
|
|
||||||
while the <literal>"warning"</literal> type will only show a
|
|
||||||
warning.
|
|
||||||
'';
|
|
||||||
type = types.enum [ "error" "warning" ];
|
|
||||||
default = "error";
|
|
||||||
example = "warning";
|
|
||||||
};
|
|
||||||
|
|
||||||
options.message = mkOption {
|
|
||||||
description = ''
|
|
||||||
The message to display if this check triggers.
|
|
||||||
To display option names in the message, add
|
|
||||||
<literal>options</literal> to the module function arguments
|
|
||||||
and use <literal>''${options.path.to.option}</literal>.
|
|
||||||
'';
|
|
||||||
type = types.str;
|
|
||||||
example = "Enabling both \${options.services.foo.enable} and \${options.services.bar.enable} is not possible.";
|
|
||||||
};
|
|
||||||
});
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
config = {
|
config = {
|
||||||
|
@ -236,35 +154,6 @@ rec {
|
||||||
# paths, meaning recursiveUpdate will never override any value
|
# paths, meaning recursiveUpdate will never override any value
|
||||||
else recursiveUpdate freeformConfig declaredConfig;
|
else recursiveUpdate freeformConfig declaredConfig;
|
||||||
|
|
||||||
# Triggers all checks defined by _module.checks before returning its argument
|
|
||||||
triggerChecks = let
|
|
||||||
|
|
||||||
handleCheck = errors: name:
|
|
||||||
let
|
|
||||||
value = config._module.checks.${name};
|
|
||||||
show =
|
|
||||||
# Assertions with a _ prefix aren't meant to be configurable
|
|
||||||
if lib.hasPrefix "_" name then value.message
|
|
||||||
else "[${showOption prefix}${optionalString (prefix != []) "/"}${name}] ${value.message}";
|
|
||||||
in
|
|
||||||
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}";
|
|
||||||
|
|
||||||
errors = lib.foldl' handleCheck [] (lib.attrNames config._module.checks);
|
|
||||||
|
|
||||||
errorMessage = ''
|
|
||||||
Failed checks:
|
|
||||||
${lib.concatMapStringsSep "\n" (a: "- ${a}") errors}
|
|
||||||
'';
|
|
||||||
|
|
||||||
trigger = if errors == [] then null else throw errorMessage;
|
|
||||||
|
|
||||||
in builtins.seq trigger;
|
|
||||||
|
|
||||||
finalConfig = triggerChecks (removeAttrs config [ "_module" ]);
|
|
||||||
|
|
||||||
checkUnmatched =
|
checkUnmatched =
|
||||||
if config._module.check && config._module.freeformType == null && merged.unmatchedDefns != [] then
|
if config._module.check && config._module.freeformType == null && merged.unmatchedDefns != [] then
|
||||||
let
|
let
|
||||||
|
@ -284,7 +173,7 @@ rec {
|
||||||
|
|
||||||
result = builtins.seq checkUnmatched {
|
result = builtins.seq checkUnmatched {
|
||||||
inherit options;
|
inherit options;
|
||||||
config = finalConfig;
|
config = removeAttrs config [ "_module" ];
|
||||||
inherit (config) _module;
|
inherit (config) _module;
|
||||||
};
|
};
|
||||||
in result;
|
in result;
|
||||||
|
@ -625,8 +514,6 @@ rec {
|
||||||
definitions = map (def: def.value) res.defsFinal;
|
definitions = map (def: def.value) res.defsFinal;
|
||||||
files = map (def: def.file) res.defsFinal;
|
files = map (def: def.file) res.defsFinal;
|
||||||
inherit (res) isDefined;
|
inherit (res) isDefined;
|
||||||
# This allows options to be correctly displayed using `${options.path.to.it}`
|
|
||||||
__toString = _: showOption loc;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
# Merge definitions of a value of a given type.
|
# Merge definitions of a value of a given type.
|
||||||
|
@ -886,15 +773,14 @@ rec {
|
||||||
visible = false;
|
visible = false;
|
||||||
apply = x: throw "The option `${showOption optionName}' can no longer be used since it's been removed. ${replacementInstructions}";
|
apply = x: throw "The option `${showOption optionName}' can no longer be used since it's been removed. ${replacementInstructions}";
|
||||||
});
|
});
|
||||||
config._module.checks =
|
config.assertions =
|
||||||
let opt = getAttrFromPath optionName options; in {
|
let opt = getAttrFromPath optionName options; in [{
|
||||||
${"removed-" + showOption optionName} = lib.mkIf opt.isDefined {
|
assertion = !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}
|
||||||
'';
|
'';
|
||||||
};
|
}];
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Return a module that causes a warning to be shown if the
|
/* Return a module that causes a warning to be shown if the
|
||||||
|
@ -955,18 +841,14 @@ rec {
|
||||||
})) from);
|
})) from);
|
||||||
|
|
||||||
config = {
|
config = {
|
||||||
_module.checks =
|
warnings = filter (x: x != "") (map (f:
|
||||||
let warningMessages = map (f:
|
let val = getAttrFromPath f config;
|
||||||
let val = getAttrFromPath f config;
|
opt = getAttrFromPath f options;
|
||||||
opt = getAttrFromPath f options;
|
in
|
||||||
in {
|
optionalString
|
||||||
${"merged" + showOption f} = lib.mkIf (val != "_mkMergedOptionModule") {
|
(val != "_mkMergedOptionModule")
|
||||||
type = "warning";
|
"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.";
|
) from);
|
||||||
};
|
|
||||||
}
|
|
||||||
) from;
|
|
||||||
in mkMerge warningMessages;
|
|
||||||
} // setAttrByPath to (mkMerge
|
} // setAttrByPath to (mkMerge
|
||||||
(optional
|
(optional
|
||||||
(any (f: (getAttrFromPath f config) != "_mkMergedOptionModule") from)
|
(any (f: (getAttrFromPath f config) != "_mkMergedOptionModule") from)
|
||||||
|
@ -1025,10 +907,8 @@ rec {
|
||||||
});
|
});
|
||||||
config = mkMerge [
|
config = mkMerge [
|
||||||
{
|
{
|
||||||
_module.checks.${"renamed-" + showOption from} = mkIf (warn && fromOpt.isDefined) {
|
warnings = optional (warn && fromOpt.isDefined)
|
||||||
type = "warning";
|
"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}'.";
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
(if withPriority
|
(if withPriority
|
||||||
then mkAliasAndWrapDefsWithPriority (setAttrByPath to) fromOpt
|
then mkAliasAndWrapDefsWithPriority (setAttrByPath to) fromOpt
|
||||||
|
|
|
@ -192,7 +192,7 @@ rec {
|
||||||
let ss = opt.type.getSubOptions opt.loc;
|
let ss = opt.type.getSubOptions opt.loc;
|
||||||
in if ss != {} then optionAttrSetToDocList' opt.loc ss else [];
|
in if ss != {} then optionAttrSetToDocList' opt.loc ss else [];
|
||||||
in
|
in
|
||||||
[ docOption ] ++ optionals (docOption.visible && ! docOption.internal) subOptions) (collect isOption options);
|
[ docOption ] ++ optionals docOption.visible subOptions) (collect isOption options);
|
||||||
|
|
||||||
|
|
||||||
/* This function recursively removes all derivation attributes from
|
/* This function recursively removes all derivation attributes from
|
||||||
|
|
|
@ -655,7 +655,7 @@ runTests {
|
||||||
modules = [ module ];
|
modules = [ module ];
|
||||||
}).options;
|
}).options;
|
||||||
|
|
||||||
locs = filter (o: ! o.internal) (optionAttrSetToDocList (removeAttrs options [ "_module" ]));
|
locs = filter (o: ! o.internal) (optionAttrSetToDocList options);
|
||||||
in map (o: o.loc) locs;
|
in map (o: o.loc) locs;
|
||||||
expected = [ [ "foo" ] [ "foo" "<name>" "bar" ] [ "foo" "bar" ] ];
|
expected = [ [ "foo" ] [ "foo" "<name>" "bar" ] [ "foo" "bar" ] ];
|
||||||
};
|
};
|
||||||
|
|
|
@ -27,50 +27,37 @@ reportFailure() {
|
||||||
fail=$((fail + 1))
|
fail=$((fail + 1))
|
||||||
}
|
}
|
||||||
|
|
||||||
checkConfigCodeOutErr() {
|
|
||||||
local expectedExit=$1
|
|
||||||
shift;
|
|
||||||
local outputContains=$1
|
|
||||||
shift;
|
|
||||||
local errorContains=$1
|
|
||||||
shift;
|
|
||||||
out=$(mktemp)
|
|
||||||
err=$(mktemp)
|
|
||||||
evalConfig "$@" 1>"$out" 2>"$err"
|
|
||||||
if [[ "$?" -ne "$expectedExit" ]]; then
|
|
||||||
echo 2>&1 "error: Expected exit code $expectedExit while evaluating"
|
|
||||||
reportFailure "$@"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -n "$outputContains" ]] && ! grep -zP --silent "$outputContains" "$out"; then
|
|
||||||
echo 2>&1 "error: Expected output matching '$outputContains', while evaluating"
|
|
||||||
reportFailure "$@"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -n "$errorContains" ]] && ! grep -zP --silent "$errorContains" "$err"; then
|
|
||||||
echo 2>&1 "error: Expected error matching '$errorContains', while evaluating"
|
|
||||||
reportFailure "$@"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
pass=$((pass + 1))
|
|
||||||
|
|
||||||
# Clean up temp files
|
|
||||||
rm "$out" "$err"
|
|
||||||
}
|
|
||||||
|
|
||||||
checkConfigOutput() {
|
checkConfigOutput() {
|
||||||
local outputContains=$1
|
local outputContains=$1
|
||||||
shift;
|
shift;
|
||||||
checkConfigCodeOutErr 0 "$outputContains" "" "$@"
|
if evalConfig "$@" 2>/dev/null | grep --silent "$outputContains" ; then
|
||||||
|
pass=$((pass + 1))
|
||||||
|
return 0;
|
||||||
|
else
|
||||||
|
echo 2>&1 "error: Expected result matching '$outputContains', while evaluating"
|
||||||
|
reportFailure "$@"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
checkConfigError() {
|
checkConfigError() {
|
||||||
local errorContains=$1
|
local errorContains=$1
|
||||||
|
local err=""
|
||||||
shift;
|
shift;
|
||||||
checkConfigCodeOutErr 1 "" "$errorContains" "$@"
|
if err==$(evalConfig "$@" 2>&1 >/dev/null); then
|
||||||
|
echo 2>&1 "error: Expected error code, got exit code 0, while evaluating"
|
||||||
|
reportFailure "$@"
|
||||||
|
return 1
|
||||||
|
else
|
||||||
|
if echo "$err" | grep -zP --silent "$errorContains" ; then
|
||||||
|
pass=$((pass + 1))
|
||||||
|
return 0;
|
||||||
|
else
|
||||||
|
echo 2>&1 "error: Expected error matching '$errorContains', while evaluating"
|
||||||
|
reportFailure "$@"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check boolean option.
|
# Check boolean option.
|
||||||
|
@ -275,29 +262,6 @@ checkConfigOutput true config.value.mkbefore ./types-anything/mk-mods.nix
|
||||||
checkConfigOutput 1 config.value.nested.foo ./types-anything/mk-mods.nix
|
checkConfigOutput 1 config.value.nested.foo ./types-anything/mk-mods.nix
|
||||||
checkConfigOutput baz config.value.nested.bar.baz ./types-anything/mk-mods.nix
|
checkConfigOutput baz config.value.nested.bar.baz ./types-anything/mk-mods.nix
|
||||||
|
|
||||||
## Module assertions
|
|
||||||
# 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 or condition is true
|
|
||||||
checkConfigOutput '{ }' config ./assertions/condition-true.nix
|
|
||||||
checkConfigOutput '{ }' config ./assertions/enable-false.nix
|
|
||||||
|
|
||||||
# Warnings should be displayed on standard error
|
|
||||||
checkConfigCodeOutErr 0 '{ }' 'warning: \[test\] Warning message' config ./assertions/warning.nix
|
|
||||||
|
|
||||||
# Check that multiple assertions and warnings can be triggered at once
|
|
||||||
checkConfigError 'Failed checks:\n- \[test1\] Assertion 1 failed\n- \[test2\] Assertion 2 failed' config ./assertions/multi.nix
|
|
||||||
checkConfigError 'trace: warning: \[test3\] Warning 3 failed\ntrace: warning: \[test4\] Warning 4 failed' config ./assertions/multi.nix
|
|
||||||
|
|
||||||
# Submodules should be able to trigger assertions and display the submodule prefix in their error
|
|
||||||
checkConfigError 'Failed checks:\n- \[foo/test\] Assertion failed' config.foo ./assertions/submodule.nix
|
|
||||||
checkConfigError 'Failed checks:\n- \[foo.bar/test\] Assertion failed' config.foo.bar ./assertions/submodule-attrsOf.nix
|
|
||||||
checkConfigError 'Failed checks:\n- \[foo.bar.baz/test\] Assertion failed' config.foo.bar.baz ./assertions/submodule-attrsOf-attrsOf.nix
|
|
||||||
|
|
||||||
# Assertions with an attribute starting with _ shouldn't have their name displayed
|
|
||||||
checkConfigError 'Failed checks:\n- Assertion failed' config ./assertions/underscore-attributes.nix
|
|
||||||
|
|
||||||
cat <<EOF
|
cat <<EOF
|
||||||
====== module tests ======
|
====== module tests ======
|
||||||
$pass Pass
|
$pass Pass
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
{
|
|
||||||
|
|
||||||
_module.checks.test = {
|
|
||||||
check = true;
|
|
||||||
message = "Assertion failed";
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
{
|
|
||||||
|
|
||||||
_module.checks.test = {
|
|
||||||
enable = false;
|
|
||||||
check = false;
|
|
||||||
message = "Assertion failed";
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,23 +0,0 @@
|
||||||
{
|
|
||||||
|
|
||||||
_module.checks = {
|
|
||||||
test1 = {
|
|
||||||
check = false;
|
|
||||||
message = "Assertion 1 failed";
|
|
||||||
};
|
|
||||||
test2 = {
|
|
||||||
check = false;
|
|
||||||
message = "Assertion 2 failed";
|
|
||||||
};
|
|
||||||
test3 = {
|
|
||||||
check = false;
|
|
||||||
message = "Warning 3 failed";
|
|
||||||
type = "warning";
|
|
||||||
};
|
|
||||||
test4 = {
|
|
||||||
check = false;
|
|
||||||
message = "Warning 4 failed";
|
|
||||||
type = "warning";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,6 +0,0 @@
|
||||||
{
|
|
||||||
_module.checks.test = {
|
|
||||||
check = false;
|
|
||||||
message = "Assertion failed";
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
{ lib, ... }: {
|
|
||||||
|
|
||||||
options.foo = lib.mkOption {
|
|
||||||
default = { bar.baz = {}; };
|
|
||||||
type = lib.types.attrsOf (lib.types.attrsOf (lib.types.submodule {
|
|
||||||
_module.checks.test = {
|
|
||||||
check = false;
|
|
||||||
message = "Assertion failed";
|
|
||||||
};
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
{ lib, ... }: {
|
|
||||||
|
|
||||||
options.foo = lib.mkOption {
|
|
||||||
default = { bar = {}; };
|
|
||||||
type = lib.types.attrsOf (lib.types.submodule {
|
|
||||||
_module.checks.test = {
|
|
||||||
check = false;
|
|
||||||
message = "Assertion failed";
|
|
||||||
};
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
{ lib, ... }: {
|
|
||||||
|
|
||||||
options.foo = lib.mkOption {
|
|
||||||
default = {};
|
|
||||||
type = lib.types.submodule {
|
|
||||||
_module.checks.test = {
|
|
||||||
check = false;
|
|
||||||
message = "Assertion failed";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
{
|
|
||||||
|
|
||||||
_module.checks._test = {
|
|
||||||
check = false;
|
|
||||||
message = "Assertion failed";
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
{
|
|
||||||
|
|
||||||
_module.checks.test = {
|
|
||||||
check = false;
|
|
||||||
type = "warning";
|
|
||||||
message = "Warning message";
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
|
@ -3,155 +3,72 @@
|
||||||
xmlns:xi="http://www.w3.org/2001/XInclude"
|
xmlns:xi="http://www.w3.org/2001/XInclude"
|
||||||
version="5.0"
|
version="5.0"
|
||||||
xml:id="sec-assertions">
|
xml:id="sec-assertions">
|
||||||
<title>Evaluation Checks</title>
|
<title>Warnings and Assertions</title>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
When configuration problems are detectable in a module, it is a good idea to
|
When configuration problems are detectable in a module, it is a good idea to
|
||||||
write a check for catching it early. Doing so can provide clear feedback to
|
write an assertion or warning. Doing so provides clear feedback to the user
|
||||||
the user and can prevent errors before the build.
|
and prevents errors after the build.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
Although Nix has the <literal>abort</literal> and
|
Although Nix has the <literal>abort</literal> and
|
||||||
<literal>builtins.trace</literal>
|
<literal>builtins.trace</literal>
|
||||||
<link xlink:href="https://nixos.org/nix/manual/#ssec-builtins">functions</link>
|
<link xlink:href="https://nixos.org/nix/manual/#ssec-builtins">functions</link>
|
||||||
to perform such tasks generally, they are not ideally suited for NixOS
|
to perform such tasks, they are not ideally suited for NixOS modules. Instead
|
||||||
modules. Instead of these functions, you can declare your evaluation checks
|
of these functions, you can declare your warnings and assertions using the
|
||||||
using the NixOS module system.
|
NixOS module system.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
<section xml:id="sec-assertions-define">
|
<section xml:id="sec-assertions-warnings">
|
||||||
<title>Defining Checks</title>
|
<title>Warnings</title>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
Checks can be defined using the <xref linkend="opt-_module.checks"/> option.
|
This is an example of using <literal>warnings</literal>.
|
||||||
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 an option path. Here is an example showing how this can be
|
|
||||||
done.
|
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
<programlisting>
|
<programlisting>
|
||||||
{ config, options, ... }: {
|
<![CDATA[
|
||||||
_module.checks.gpgSshAgent = {
|
{ config, lib, ... }:
|
||||||
check = config.programs.gnupg.agent.enableSSHSupport -> !config.programs.ssh.startAgent;
|
{
|
||||||
message = "If you have ${options.programs.gnupg.agent.enableSSHSupport} enabled,"
|
config = lib.mkIf config.services.foo.enable {
|
||||||
+ " you can't enable ${options.programs.ssh.startAgent} as well!";
|
warnings =
|
||||||
};
|
if config.services.foo.bar
|
||||||
|
then [ ''You have enabled the bar feature of the foo service.
|
||||||
_module.checks.grafanaPassword = {
|
This is known to cause some specific problems in certain situations.
|
||||||
check = config.services.grafana.database.password == "";
|
'' ]
|
||||||
message = "The grafana password defined with ${options.services.grafana.database.password}"
|
else [];
|
||||||
+ " will be stored as plaintext in the Nix store!";
|
}
|
||||||
# This is a non-fatal warning
|
|
||||||
type = "warning";
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
]]>
|
||||||
</programlisting>
|
</programlisting>
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section xml:id="sec-assertions-ignoring">
|
<section xml:id="sec-assertions-assertions">
|
||||||
<title>Ignoring Checks</title>
|
<title>Assertions</title>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
Sometimes you can get failing checks that don't apply to your specific case
|
This example, extracted from the
|
||||||
and you wish to ignore them, or at least make errors non-fatal. You can do so
|
<link xlink:href="https://github.com/NixOS/nixpkgs/blob/release-17.09/nixos/modules/services/logging/syslogd.nix">
|
||||||
for all checks defined using <xref linkend="opt-_module.checks"/> by
|
<literal>syslogd</literal> module </link> shows how to use
|
||||||
using the attribute name of the definition, which is conveniently printed
|
<literal>assertions</literal>. Since there can only be one active syslog
|
||||||
using <literal>[...]</literal> when the check is triggered. For above
|
daemon at a time, an assertion is useful to prevent such a broken system
|
||||||
example, the evaluation output when the checks are triggered looks as
|
from being built.
|
||||||
follows:
|
|
||||||
</para>
|
|
||||||
|
|
||||||
<programlisting>
|
|
||||||
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] If you have programs.gnupg.agent.enableSSHSupport
|
|
||||||
enabled, you can't enable programs.ssh.startAgent as well!
|
|
||||||
</programlisting>
|
|
||||||
|
|
||||||
<para>
|
|
||||||
The <literal>[grafanaPassword]</literal> and <literal>[gpgSshAgent]</literal>
|
|
||||||
strings tell you that these were defined under the <literal>grafanaPassword
|
|
||||||
</literal> and <literal>gpgSshAgent</literal> attributes of
|
|
||||||
<xref linkend="opt-_module.checks"/> respectively. With this knowledge
|
|
||||||
you can adjust them to your liking:
|
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
<programlisting>
|
<programlisting>
|
||||||
|
<![CDATA[
|
||||||
|
{ config, lib, ... }:
|
||||||
{
|
{
|
||||||
# Change the error into a non-fatal warning
|
config = lib.mkIf config.services.syslogd.enable {
|
||||||
_module.checks.gpgSshAgent.type = "warning";
|
assertions =
|
||||||
|
[ { assertion = !config.services.rsyslogd.enable;
|
||||||
# We don't care about this warning, disable it
|
message = "rsyslogd conflicts with syslogd";
|
||||||
_module.checks.grafanaPassword.enable = false;
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
]]>
|
||||||
</programlisting>
|
</programlisting>
|
||||||
|
|
||||||
|
|
||||||
</section>
|
|
||||||
<section xml:id="sec-assertions-submodules">
|
|
||||||
<title>Checks in Submodules</title>
|
|
||||||
|
|
||||||
<para>
|
|
||||||
Evaluation checks can be defined within submodules in the same way. Here is an example:
|
|
||||||
</para>
|
|
||||||
|
|
||||||
<programlisting>
|
|
||||||
{ lib, ... }: {
|
|
||||||
|
|
||||||
options.myServices = lib.mkOption {
|
|
||||||
type = lib.types.attrsOf (lib.types.submodule ({ config, options, ... }: {
|
|
||||||
options.port = lib.mkOption {};
|
|
||||||
|
|
||||||
config._module.checks.portConflict = {
|
|
||||||
check = config.port != 80;
|
|
||||||
message = "Port ${toString config.port} defined using"
|
|
||||||
+ " ${options.port} is usually used for HTTP";
|
|
||||||
type = "warning";
|
|
||||||
};
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
</programlisting>
|
|
||||||
|
|
||||||
<para>
|
|
||||||
When this check is triggered, it shows both the submodule path along with
|
|
||||||
the check attribute within that submodule, joined by a
|
|
||||||
<literal>/</literal>. Note also how <literal>${options.port}</literal>
|
|
||||||
correctly shows the context of the option.
|
|
||||||
</para>
|
|
||||||
|
|
||||||
<programlisting>
|
|
||||||
trace: warning: [myServices.foo/portConflict] Port 80 defined using
|
|
||||||
myServices.foo.port is usually used for HTTP
|
|
||||||
</programlisting>
|
|
||||||
|
|
||||||
<para>
|
|
||||||
Therefore to disable such a check, you can do so by changing the
|
|
||||||
<xref linkend="opt-_module.checks"/> option within the
|
|
||||||
<literal>myServices.foo</literal> submodule:
|
|
||||||
</para>
|
|
||||||
|
|
||||||
<programlisting>
|
|
||||||
{
|
|
||||||
myServices.foo._module.checks.portConflict.enable = false;
|
|
||||||
}
|
|
||||||
</programlisting>
|
|
||||||
|
|
||||||
<note>
|
|
||||||
<para>
|
|
||||||
Checks defined in submodules under <literal>types.listOf</literal> can't be
|
|
||||||
ignored, since there's no way to change previously defined list items.
|
|
||||||
</para>
|
|
||||||
</note>
|
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
</section>
|
</section>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{ lib, config, ... }:
|
{ lib, ... }:
|
||||||
|
|
||||||
with lib;
|
with lib;
|
||||||
|
|
||||||
|
@ -30,18 +30,5 @@ with lib;
|
||||||
};
|
};
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
config._module.checks = lib.listToAttrs (lib.imap1 (n: value:
|
|
||||||
let
|
|
||||||
name = "_${toString n}";
|
|
||||||
isWarning = lib.isString value;
|
|
||||||
result = {
|
|
||||||
check = if isWarning then false else value.assertion;
|
|
||||||
type = if isWarning then "warning" else "error";
|
|
||||||
message = if isWarning then value else value.message;
|
|
||||||
};
|
|
||||||
in nameValuePair name result
|
|
||||||
) (config.assertions ++ config.warnings));
|
|
||||||
|
|
||||||
# impl of assertions is in <nixpkgs/nixos/modules/system/activation/top-level.nix>
|
# impl of assertions is in <nixpkgs/nixos/modules/system/activation/top-level.nix>
|
||||||
}
|
}
|
||||||
|
|
|
@ -117,10 +117,18 @@ let
|
||||||
perl = "${pkgs.perl}/bin/perl " + (concatMapStringsSep " " (lib: "-I${lib}/${pkgs.perl.libPrefix}") (with pkgs.perlPackages; [ FileSlurp NetDBus XMLParser XMLTwig ]));
|
perl = "${pkgs.perl}/bin/perl " + (concatMapStringsSep " " (lib: "-I${lib}/${pkgs.perl.libPrefix}") (with pkgs.perlPackages; [ FileSlurp NetDBus XMLParser XMLTwig ]));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# Handle assertions and warnings
|
||||||
|
|
||||||
|
failedAssertions = map (x: x.message) (filter (x: !x.assertion) config.assertions);
|
||||||
|
|
||||||
|
baseSystemAssertWarn = if failedAssertions != []
|
||||||
|
then throw "\nFailed assertions:\n${concatStringsSep "\n" (map (x: "- ${x}") failedAssertions)}"
|
||||||
|
else showWarnings config.warnings baseSystem;
|
||||||
|
|
||||||
# Replace runtime dependencies
|
# Replace runtime dependencies
|
||||||
system = fold ({ oldDependency, newDependency }: drv:
|
system = fold ({ oldDependency, newDependency }: drv:
|
||||||
pkgs.replaceDependency { inherit oldDependency newDependency drv; }
|
pkgs.replaceDependency { inherit oldDependency newDependency drv; }
|
||||||
) baseSystem config.system.replaceRuntimeDependencies;
|
) baseSystemAssertWarn config.system.replaceRuntimeDependencies;
|
||||||
|
|
||||||
in
|
in
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue