From c4fb54e92a10f04bb70b31b397a50fdbc203bc66 Mon Sep 17 00:00:00 2001 From: Silvan Mosberger Date: Wed, 2 Sep 2020 19:23:39 +0200 Subject: nixos/docs: Update assertion docs for new module-builtin ones --- nixos/doc/manual/development/assertions.xml | 129 ++++++++++++++++++++-------- 1 file changed, 94 insertions(+), 35 deletions(-) (limited to 'nixos/doc') diff --git a/nixos/doc/manual/development/assertions.xml b/nixos/doc/manual/development/assertions.xml index 32f90cf2e7c47..91506ba65a144 100644 --- a/nixos/doc/manual/development/assertions.xml +++ b/nixos/doc/manual/development/assertions.xml @@ -8,7 +8,7 @@ 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 prevents errors after the build. + and can prevent errors before the build. @@ -20,55 +20,114 @@ NixOS module system. -
- Warnings +
+ Defining Warnings and Assertions - This is an example of using warnings. + 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. - +
-
- Assertions +
+ 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! + - This example, extracted from the - - syslogd module shows how to use - assertions. Since there can only be one active syslog - daemon at a time, an assertion is useful to prevent such a broken system - from being built. + 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: - + + +
+
+ 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. + + +
-- cgit 1.4.1 From c9cc8969b4d4adeb389837d1cc85cc73a8272f55 Mon Sep 17 00:00:00 2001 From: Silvan Mosberger Date: Mon, 30 Nov 2020 20:04:03 +0100 Subject: lib/modules: Rename _module.assertions to _module.checks --- lib/modules.nix | 66 ++++++++++--------- lib/tests/modules.sh | 16 ++--- lib/tests/modules/assertions/enable-false.nix | 2 +- lib/tests/modules/assertions/enable-lazy.nix | 2 +- lib/tests/modules/assertions/multi.nix | 2 +- lib/tests/modules/assertions/non-cascading.nix | 2 +- lib/tests/modules/assertions/simple.nix | 2 +- .../assertions/submodule-attrsOf-attrsOf.nix | 2 +- lib/tests/modules/assertions/submodule-attrsOf.nix | 2 +- lib/tests/modules/assertions/submodule.nix | 2 +- lib/tests/modules/assertions/trigger-lazy.nix | 2 +- lib/tests/modules/assertions/trigger-submodule.nix | 2 +- .../modules/assertions/underscore-attributes.nix | 2 +- lib/tests/modules/assertions/warning.nix | 2 +- nixos/doc/manual/development/assertions.xml | 74 +++++++++++++++------- nixos/modules/misc/assertions.nix | 4 +- 16 files changed, 108 insertions(+), 76 deletions(-) (limited to 'nixos/doc') diff --git a/lib/modules.nix b/lib/modules.nix index e3f7ca3581ca6..9aa638231bf42 100644 --- a/lib/modules.nix +++ b/lib/modules.nix @@ -103,7 +103,13 @@ rec { type = types.bool; internal = prefix != []; default = check; - description = "Whether to check whether all option definitions have matching declarations."; + description = '' + Whether to check whether all option definitions have matching + declarations. + + Note that this has nothing to do with the similarly named + option + ''; }; _module.freeformType = mkOption { @@ -123,11 +129,11 @@ rec { ''; }; - _module.assertions = mkOption { + _module.checks = mkOption { description = '' - Assertions and warnings to trigger during module evaluation. The + Evaluation checks to trigger during module evaluation. The attribute name will be displayed when it is triggered, allowing - users to disable/change these assertions again if necessary. See + users to disable/change these checks if necessary. See the section on Warnings and Assertions in the manual for more information. ''; @@ -151,7 +157,7 @@ rec { # TODO: Rename to assertion? Or allow also setting assertion? options.enable = mkOption { description = '' - Whether to enable this assertion. + Whether to enable this check. This is the inverse of asserting a condition: If a certain condition should be true, then this @@ -164,7 +170,7 @@ rec { options.type = mkOption { description = '' - The type of the assertion. The default + The type of the check. The default "error" type will cause evaluation to fail, while the "warning" type will only show a warning. @@ -176,7 +182,7 @@ rec { options.message = mkOption { description = '' - The assertion message to display if this assertion triggers. + The message to display if this check triggers. To display option names in the message, add options to the module function arguments and use ''${options.path.to.option}. @@ -190,24 +196,24 @@ rec { options.triggerPath = mkOption { description = '' The config path which when evaluated should - trigger this assertion. By default this is + trigger this check. By default this is [], meaning evaluating - config at all will trigger the assertion. + config at all will trigger the check. On NixOS this default is changed to [ "system" "build" "toplevel" such that - only a system evaluation triggers the assertions. + only a system evaluation triggers the checks. Evaluating config from within the current module evaluation doesn't cause a trigger. Only accessing it from outside will do that. This means it's easy to miss - assertions if this option doesn't have an externally-accessed + failing checks if this option doesn't have an externally-accessed value. ''; # Mark as internal as it's easy to misuse it internal = true; type = types.uniq (types.listOf types.str); - # Default to [], causing assertions to be triggered when + # Default to [], causing checks to be triggered when # anything is evaluated. This is a safe and convenient default. default = []; example = [ "system" "build" "vm" ]; @@ -253,13 +259,13 @@ rec { else recursiveUpdate freeformConfig declaredConfig; /* - Inject a list of assertions into a config value, corresponding to their + Inject a list of checks into a config value, corresponding to their triggerPath (meaning when that path is accessed from the result of this - function, the assertion triggers). + function, the check triggers). */ - injectAssertions = assertions: config: let - # Partition into assertions that are triggered on this level and ones that aren't - parted = lib.partition (a: length a.triggerPath == 0) assertions; + injectChecks = checks: config: let + # Partition into checks that are triggered on this level and ones that aren't + parted = lib.partition (a: length a.triggerPath == 0) checks; # From the ones that are triggered, filter out ones that aren't enabled # and group into warnings/errors @@ -270,45 +276,45 @@ rec { errorTrigger = value: if byType.error or [] == [] then value else throw '' - Failed assertions: + Failed checks: ${lib.concatMapStringsSep "\n" (a: "- ${a.show}") byType.error} ''; # Trigger for both warnings and errors trigger = value: warningTrigger (errorTrigger value); - # From the non-triggered assertions, split off the first element of triggerPath - # to get a mapping from nested attributes to a list of assertions for that attribute + # From the non-triggered checks, split off the first element of triggerPath + # to get a mapping from nested attributes to a list of checks for that attribute nested = lib.zipAttrs (map (a: { ${head a.triggerPath} = a // { triggerPath = lib.tail a.triggerPath; }; }) parted.wrong); - # Recursively inject assertions if config is an attribute set and we - # have assertions under its attributes + # Recursively inject checks if config is an attribute set and we + # have checks under its attributes result = if isAttrs config then mapAttrs (name: value: if nested ? ${name} - then injectAssertions nested.${name} value + then injectChecks nested.${name} value else value ) config else config; in trigger result; - # List of assertions for this module evaluation, where each assertion also + # List of checks for this module evaluation, where each check also # has a `show` attribute for how to show it if triggered - assertions = mapAttrsToList (name: value: + checks = mapAttrsToList (name: value: let id = if lib.hasPrefix "_" name then "" else "[${showOption prefix}${optionalString (prefix != []) "/"}${name}] "; in value // { show = "${id}${value.message}"; } - ) config._module.assertions; + ) config._module.checks; - finalConfig = injectAssertions assertions (removeAttrs config [ "_module" ]); + finalConfig = injectChecks checks (removeAttrs config [ "_module" ]); checkUnmatched = if config._module.check && config._module.freeformType == null && merged.unmatchedDefns != [] then @@ -931,7 +937,7 @@ rec { visible = false; apply = x: throw "The option `${showOption optionName}' can no longer be used since it's been removed. ${replacementInstructions}"; }); - config._module.assertions = + config._module.checks = let opt = getAttrFromPath optionName options; in { ${showOption optionName} = { enable = mkDefault opt.isDefined; @@ -1001,7 +1007,7 @@ rec { })) from); config = { - _module.assertions = + _module.checks = let warningMessages = map (f: let val = getAttrFromPath f config; opt = getAttrFromPath f options; @@ -1072,7 +1078,7 @@ rec { }); config = mkMerge [ { - _module.assertions.${showOption from} = { + _module.checks.${showOption from} = { enable = mkDefault (warn && fromOpt.isDefined); type = "warning"; message = "The option `${showOption from}' defined in ${showFiles fromOpt.files} has been renamed to `${showOption to}'."; diff --git a/lib/tests/modules.sh b/lib/tests/modules.sh index 65eb91c992768..43bcabdf8167f 100755 --- a/lib/tests/modules.sh +++ b/lib/tests/modules.sh @@ -277,9 +277,9 @@ 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 assertions:\n- \[test\] Assertion failed' config ./assertions/simple.nix +checkConfigError 'Failed checks:\n- \[test\] Assertion failed' config ./assertions/simple.nix # Check that assertions are only triggered if they have a triggerPath that's evaluated -checkConfigError 'Failed assertions:\n- \[test\] Assertion failed' config.foo ./assertions/trigger-lazy.nix +checkConfigError 'Failed checks:\n- \[test\] Assertion failed' config.foo ./assertions/trigger-lazy.nix checkConfigOutput true config.bar ./assertions/trigger-lazy.nix # The assertions enable condition should only be evaluated if the trigger is evaluated @@ -294,23 +294,23 @@ checkConfigCodeOutErr 0 '{ }' 'warning: \[test\] Warning message' config ./asser # A triggerPath can be set to a submodule path checkConfigOutput '{ baz = ; }' config.foo.bar ./assertions/trigger-submodule.nix -checkConfigError 'Failed assertions:\n- \[test\] Assertion failed' config.foo.bar.baz ./assertions/trigger-submodule.nix +checkConfigError 'Failed checks:\n- \[test\] Assertion failed' config.foo.bar.baz ./assertions/trigger-submodule.nix # Check that multiple assertions and warnings can be triggered at once -checkConfigError 'Failed assertions:\n- \[test1\] Assertion 1 failed\n- \[test2\] Assertion 2 failed' config ./assertions/multi.nix +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 assertions:\n- \[foo/test\] Assertion failed' config.foo ./assertions/submodule.nix -checkConfigError 'Failed assertions:\n- \[foo.bar/test\] Assertion failed' config.foo.bar ./assertions/submodule-attrsOf.nix -checkConfigError 'Failed assertions:\n- \[foo.bar.baz/test\] Assertion failed' config.foo.bar.baz ./assertions/submodule-attrsOf-attrsOf.nix +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 aren't triggered when the trigger path is only evaluated from within the same module evaluation # This behavior is necessary to allow assertions to depend on config values. This could potentially be changed in the future if all of NixOS' assertions are rewritten to not depend on any config values checkConfigOutput true config.bar ./assertions/non-cascading.nix # Assertions with an attribute starting with _ shouldn't have their name displayed -checkConfigError 'Failed assertions:\n- Assertion failed' config ./assertions/underscore-attributes.nix +checkConfigError 'Failed checks:\n- Assertion failed' config ./assertions/underscore-attributes.nix cat < - Warnings and Assertions + Evaluation Checks 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. + write a check for catching it early. Doing so can provide 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. + to perform such tasks generally, they are not ideally suited for NixOS + modules. Instead of these functions, you can declare your evaluation checks + using the NixOS module system.
- Defining Warnings and Assertions + Defining Checks - 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. + Checks can be defined using the option. + Each check 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 check 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 = { + _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}!"; }; - _module.assertions.grafanaPassword = { + _module.checks.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!"; @@ -48,41 +58,51 @@
- Ignoring Warnings and Assertions + Ignoring Checks - 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: + Sometimes you can get failing checks that don't apply to your specific case + and you wish to ignore them, or at least make errors non-fatal. You can do so + for all checks defined using by + using the attribute name of the definition, which is conveniently printed + using [...] when the check is triggered. For above + example, the evaluation output when the checks 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: +error: Failed checks: - [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: + 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"; + # Change the error into a non-fatal warning + _module.checks.gpgSshAgent.type = "warning"; # We don't care about this warning, disable it - _module.assertions.grafanaPassword.enable = lib.mkForce false; + _module.checks.grafanaPassword.enable = lib.mkForce false; }
- Warnings and Assertions in Submodules + Checks in Submodules - Warnings and assertions can be defined within submodules in the same way. Here is an example: + Evaluation checks can be defined within submodules in the same way. Here is an example: @@ -92,7 +112,7 @@ error: Failed assertions: type = lib.types.attrsOf (lib.types.submodule ({ config, options, ... }: { options.port = lib.mkOption {}; - config._module.assertions.portConflict = { + config._module.checks.portConflict = { enable = config.port == 80; message = "Port ${toString config.port} defined using" + " ${options.port} is usually used for HTTP"; @@ -105,7 +125,10 @@ error: Failed assertions: - 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. + When this check is triggered, it shows both the submodule path along with + the check attribute within that submodule, joined by a + /. Note also how ${options.port} + correctly shows the context of the option. @@ -114,18 +137,21 @@ trace: warning: [myServices.foo/portConflict] Port 80 defined using - Therefore to disable such an assertion, you can do so by changing the option within the myServices.foo submodule: + Therefore to disable such a check, you can do so by changing the + option within the + myServices.foo submodule: { lib, ... }: { - myServices.foo._module.assertions.portConflict.enable = lib.mkForce false; + myServices.foo._module.checks.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. + Checks defined in submodules under types.listOf can't be + ignored, since there's no way to change previously defined list items. diff --git a/nixos/modules/misc/assertions.nix b/nixos/modules/misc/assertions.nix index e931611247f22..e8b1f5afca3f1 100644 --- a/nixos/modules/misc/assertions.nix +++ b/nixos/modules/misc/assertions.nix @@ -29,7 +29,7 @@ with lib; ''; }; - _module.assertions = mkOption { + _module.checks = mkOption { type = types.attrsOf (types.submodule { triggerPath = mkDefault [ "system" "build" "toplevel" ]; }); @@ -37,7 +37,7 @@ with lib; }; - config._module.assertions = lib.listToAttrs (lib.imap1 (n: value: + config._module.checks = lib.listToAttrs (lib.imap1 (n: value: let name = "_${toString n}"; isWarning = lib.isString value; -- cgit 1.4.1 From 767d80099cd8418b3cc7338eb24f9217fedb6449 Mon Sep 17 00:00:00 2001 From: Silvan Mosberger Date: Mon, 30 Nov 2020 22:38:56 +0100 Subject: 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 --- lib/modules.nix | 36 +++++++++++----------- lib/tests/modules.sh | 3 +- lib/tests/modules/assertions/condition-true.nix | 8 +++++ lib/tests/modules/assertions/enable-false.nix | 1 + lib/tests/modules/assertions/multi.nix | 8 ++--- lib/tests/modules/assertions/simple.nix | 2 +- .../assertions/submodule-attrsOf-attrsOf.nix | 2 +- lib/tests/modules/assertions/submodule-attrsOf.nix | 2 +- lib/tests/modules/assertions/submodule.nix | 2 +- .../modules/assertions/underscore-attributes.nix | 2 +- lib/tests/modules/assertions/warning.nix | 2 +- nixos/doc/manual/development/assertions.xml | 34 ++++++++++---------- nixos/modules/misc/assertions.nix | 2 +- 13 files changed, 56 insertions(+), 48 deletions(-) create mode 100644 lib/tests/modules/assertions/condition-true.nix (limited to 'nixos/doc') diff --git a/lib/modules.nix b/lib/modules.nix index 23dbe962491ba..468c373d6aa4a 100644 --- a/lib/modules.nix +++ b/lib/modules.nix @@ -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. - - This is the inverse of asserting a condition: If a certain - condition should be true, then this - option should be set to false when that - case occurs - + 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 ''${options.path.to.option}. ''; 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}'."; }; diff --git a/lib/tests/modules.sh b/lib/tests/modules.sh index 9e85c90d15c55..775be9f7209bc 100755 --- a/lib/tests/modules.sh +++ b/lib/tests/modules.sh @@ -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 diff --git a/lib/tests/modules/assertions/condition-true.nix b/lib/tests/modules/assertions/condition-true.nix new file mode 100644 index 0000000000000..7ca0817a23971 --- /dev/null +++ b/lib/tests/modules/assertions/condition-true.nix @@ -0,0 +1,8 @@ +{ + + _module.checks.test = { + check = true; + message = "Assertion failed"; + }; + +} diff --git a/lib/tests/modules/assertions/enable-false.nix b/lib/tests/modules/assertions/enable-false.nix index c326c086f03f2..11f753bb32e81 100644 --- a/lib/tests/modules/assertions/enable-false.nix +++ b/lib/tests/modules/assertions/enable-false.nix @@ -2,6 +2,7 @@ _module.checks.test = { enable = false; + check = false; message = "Assertion failed"; }; diff --git a/lib/tests/modules/assertions/multi.nix b/lib/tests/modules/assertions/multi.nix index ebbe17f3a5584..1e2e14b8643a4 100644 --- a/lib/tests/modules/assertions/multi.nix +++ b/lib/tests/modules/assertions/multi.nix @@ -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"; }; diff --git a/lib/tests/modules/assertions/simple.nix b/lib/tests/modules/assertions/simple.nix index a63b8090f9109..115d89a303622 100644 --- a/lib/tests/modules/assertions/simple.nix +++ b/lib/tests/modules/assertions/simple.nix @@ -1,6 +1,6 @@ { _module.checks.test = { - enable = true; + check = false; message = "Assertion failed"; }; } diff --git a/lib/tests/modules/assertions/submodule-attrsOf-attrsOf.nix b/lib/tests/modules/assertions/submodule-attrsOf-attrsOf.nix index a5f92aa93c7d7..27a63d1e4329c 100644 --- a/lib/tests/modules/assertions/submodule-attrsOf-attrsOf.nix +++ b/lib/tests/modules/assertions/submodule-attrsOf-attrsOf.nix @@ -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"; }; })); diff --git a/lib/tests/modules/assertions/submodule-attrsOf.nix b/lib/tests/modules/assertions/submodule-attrsOf.nix index 450cad0804dfe..aac5937cf7e59 100644 --- a/lib/tests/modules/assertions/submodule-attrsOf.nix +++ b/lib/tests/modules/assertions/submodule-attrsOf.nix @@ -4,7 +4,7 @@ default = { bar = {}; }; type = lib.types.attrsOf (lib.types.submodule { _module.checks.test = { - enable = true; + check = false; message = "Assertion failed"; }; }); diff --git a/lib/tests/modules/assertions/submodule.nix b/lib/tests/modules/assertions/submodule.nix index a46734a326bfc..4e7e0b1bd61e9 100644 --- a/lib/tests/modules/assertions/submodule.nix +++ b/lib/tests/modules/assertions/submodule.nix @@ -4,7 +4,7 @@ default = {}; type = lib.types.submodule { _module.checks.test = { - enable = true; + check = false; message = "Assertion failed"; }; }; diff --git a/lib/tests/modules/assertions/underscore-attributes.nix b/lib/tests/modules/assertions/underscore-attributes.nix index c28c9dcd9180e..f9ee5c5787b08 100644 --- a/lib/tests/modules/assertions/underscore-attributes.nix +++ b/lib/tests/modules/assertions/underscore-attributes.nix @@ -1,7 +1,7 @@ { _module.checks._test = { - enable = true; + check = false; message = "Assertion failed"; }; diff --git a/lib/tests/modules/assertions/warning.nix b/lib/tests/modules/assertions/warning.nix index 8fed9871aa2c5..72598ba3fdd55 100644 --- a/lib/tests/modules/assertions/warning.nix +++ b/lib/tests/modules/assertions/warning.nix @@ -1,7 +1,7 @@ { _module.checks.test = { - enable = true; + check = false; type = "warning"; message = "Warning message"; }; diff --git a/nixos/doc/manual/development/assertions.xml b/nixos/doc/manual/development/assertions.xml index a873345ef43a5..31d09f958af58 100644 --- a/nixos/doc/manual/development/assertions.xml +++ b/nixos/doc/manual/development/assertions.xml @@ -25,28 +25,26 @@ Checks can be defined using the option. - Each check 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 check message, you can add + Each check needs an attribute name, under which you can define a trigger + assertion using and a + message using . + For the 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 + representation of an option path. Here is an example showing how this can be done. { 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! @@ -87,12 +85,12 @@ error: Failed checks: -{ 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; } @@ -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 -{ lib, ... }: { - myServices.foo._module.checks.portConflict.enable = lib.mkForce false; +{ + myServices.foo._module.checks.portConflict.enable = false; } diff --git a/nixos/modules/misc/assertions.nix b/nixos/modules/misc/assertions.nix index 6a26a2332f25b..d7cdb32491d3c 100644 --- a/nixos/modules/misc/assertions.nix +++ b/nixos/modules/misc/assertions.nix @@ -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; }; -- cgit 1.4.1