···103 toClosureList = file: parentKey: imap1 (n: x:
104 if isAttrs x || isFunction x then
105 let key = "${parentKey}:anon-${toString n}"; in
106- unifyModuleSyntax file key (unpackSubmodule (applyIfFunction key) x args)
107 else
108 let file = toString x; key = toString x; in
109 unifyModuleSyntax file key (applyIfFunction key (import x) args));
110 in
111 builtins.genericClosure {
112 startSet = toClosureList unknownModule "" modules;
113- operator = m: toClosureList m.file m.key m.imports;
114 };
115116 /* Massage a module into canonical form, that is, a set consisting
117 of ‘options’, ‘config’ and ‘imports’ attributes. */
118 unifyModuleSyntax = file: key: m:
119- let metaSet = if m ? meta
120- then { meta = m.meta; }
121- else {};
122 in
123 if m ? config || m ? options then
124 let badAttrs = removeAttrs m ["_file" "key" "disabledModules" "imports" "options" "config" "meta"]; in
125 if badAttrs != {} then
126 throw "Module `${key}' has an unsupported attribute `${head (attrNames badAttrs)}'. This is caused by assignments to the top-level attributes `config' or `options'."
127 else
128- { file = m._file or file;
129 key = toString m.key or key;
130 disabledModules = m.disabledModules or [];
131 imports = m.imports or [];
132 options = m.options or {};
133- config = mkMerge [ (m.config or {}) metaSet ];
134 }
135 else
136- { file = m._file or file;
137 key = toString m.key or key;
138 disabledModules = m.disabledModules or [];
139 imports = m.require or [] ++ m.imports or [];
140 options = {};
141- config = mkMerge [ (removeAttrs m ["_file" "key" "disabledModules" "require" "imports"]) metaSet ];
142 };
143144 applyIfFunction = key: f: args@{ config, options, lib, ... }: if isFunction f then
···171 else
172 f;
173174- /* We have to pack and unpack submodules. We cannot wrap the expected
175- result of the function as we would no longer be able to list the arguments
176- of the submodule. (see applyIfFunction) */
177- unpackSubmodule = unpack: m: args:
178- if isType "submodule" m then
179- { _file = m.file; } // (unpack m.submodule args)
180- else unpack m args;
181-182- packSubmodule = file: m:
183- { _type = "submodule"; file = file; submodule = m; };
184-185 /* Merge a list of modules. This will recurse over the option
186 declarations in all modules, combining them into a single set.
187 At the same time, for each option declaration, it will merge the
···189 in the ‘value’ attribute of each option. */
190 mergeModules = prefix: modules:
191 mergeModules' prefix modules
192- (concatMap (m: map (config: { inherit (m) file; inherit config; }) (pushDownProperties m.config)) modules);
193194 mergeModules' = prefix: options: configs:
195 let
···223 ) {} modules;
224 # an attrset 'name' => list of submodules that declare ‘name’.
225 declsByName = byName "options" (module: option:
226- [{ inherit (module) file; options = option; }]
227 ) options;
228 # an attrset 'name' => list of submodules that define ‘name’.
229 defnsByName = byName "config" (module: value:
···250 firstOption = findFirst (m: isOption m.options) "" decls;
251 firstNonOption = findFirst (m: !isOption m.options) "" decls;
252 in
253- throw "The option `${showOption loc}' in `${firstOption.file}' is a prefix of options in `${firstNonOption.file}'."
254 else
255 mergeModules' loc decls defns
256 ))
···267268 'opts' is a list of modules. Each module has an options attribute which
269 correspond to the definition of 'loc' in 'opt.file'. */
270- mergeOptionDecls = loc: opts:
0000000271 foldl' (res: opt:
272 let t = res.type;
273 t' = opt.options.type;
···284 bothHave "apply" ||
285 (bothHave "type" && (! typesMergeable))
286 then
287- throw "The option `${showOption loc}' in `${opt.file}' is already declared in ${showFiles res.declarations}."
288 else
289 let
290 /* Add the modules of the current option to the list of modules
···293 current option declaration as the file use for the submodule. If the
294 submodule defines any filename, then we ignore the enclosing option file. */
295 options' = toList opt.options.options;
296- coerceOption = file: opt:
297- if isFunction opt then packSubmodule file opt
298- else packSubmodule file { options = opt; };
299 getSubModules = opt.options.type.getSubModules or null;
300 submodules =
301- if getSubModules != null then map (packSubmodule opt.file) getSubModules ++ res.options
302- else if opt.options ? options then map (coerceOption opt.file) options' ++ res.options
303 else res.options;
304 in opt.options // res //
305- { declarations = res.declarations ++ [opt.file];
306 options = submodules;
307 } // typeSet
308 ) { inherit loc; declarations = []; options = []; } opts;
···103 toClosureList = file: parentKey: imap1 (n: x:
104 if isAttrs x || isFunction x then
105 let key = "${parentKey}:anon-${toString n}"; in
106+ unifyModuleSyntax file key (applyIfFunction key x args)
107 else
108 let file = toString x; key = toString x; in
109 unifyModuleSyntax file key (applyIfFunction key (import x) args));
110 in
111 builtins.genericClosure {
112 startSet = toClosureList unknownModule "" modules;
113+ operator = m: toClosureList m._file m.key m.imports;
114 };
115116 /* Massage a module into canonical form, that is, a set consisting
117 of ‘options’, ‘config’ and ‘imports’ attributes. */
118 unifyModuleSyntax = file: key: m:
119+ let addMeta = config: if m ? meta
120+ then mkMerge [ config { meta = m.meta; } ]
121+ else config;
122 in
123 if m ? config || m ? options then
124 let badAttrs = removeAttrs m ["_file" "key" "disabledModules" "imports" "options" "config" "meta"]; in
125 if badAttrs != {} then
126 throw "Module `${key}' has an unsupported attribute `${head (attrNames badAttrs)}'. This is caused by assignments to the top-level attributes `config' or `options'."
127 else
128+ { _file = m._file or file;
129 key = toString m.key or key;
130 disabledModules = m.disabledModules or [];
131 imports = m.imports or [];
132 options = m.options or {};
133+ config = addMeta (m.config or {});
134 }
135 else
136+ { _file = m._file or file;
137 key = toString m.key or key;
138 disabledModules = m.disabledModules or [];
139 imports = m.require or [] ++ m.imports or [];
140 options = {};
141+ config = addMeta (removeAttrs m ["_file" "key" "disabledModules" "require" "imports"]);
142 };
143144 applyIfFunction = key: f: args@{ config, options, lib, ... }: if isFunction f then
···171 else
172 f;
17300000000000174 /* Merge a list of modules. This will recurse over the option
175 declarations in all modules, combining them into a single set.
176 At the same time, for each option declaration, it will merge the
···178 in the ‘value’ attribute of each option. */
179 mergeModules = prefix: modules:
180 mergeModules' prefix modules
181+ (concatMap (m: map (config: { file = m._file; inherit config; }) (pushDownProperties m.config)) modules);
182183 mergeModules' = prefix: options: configs:
184 let
···212 ) {} modules;
213 # an attrset 'name' => list of submodules that declare ‘name’.
214 declsByName = byName "options" (module: option:
215+ [{ inherit (module) _file; options = option; }]
216 ) options;
217 # an attrset 'name' => list of submodules that define ‘name’.
218 defnsByName = byName "config" (module: value:
···239 firstOption = findFirst (m: isOption m.options) "" decls;
240 firstNonOption = findFirst (m: !isOption m.options) "" decls;
241 in
242+ throw "The option `${showOption loc}' in `${firstOption._file}' is a prefix of options in `${firstNonOption._file}'."
243 else
244 mergeModules' loc decls defns
245 ))
···256257 'opts' is a list of modules. Each module has an options attribute which
258 correspond to the definition of 'loc' in 'opt.file'. */
259+ mergeOptionDecls =
260+ let
261+ packSubmodule = file: m:
262+ { _file = file; imports = [ m ]; };
263+ coerceOption = file: opt:
264+ if isFunction opt then packSubmodule file opt
265+ else packSubmodule file { options = opt; };
266+ in loc: opts:
267 foldl' (res: opt:
268 let t = res.type;
269 t' = opt.options.type;
···280 bothHave "apply" ||
281 (bothHave "type" && (! typesMergeable))
282 then
283+ throw "The option `${showOption loc}' in `${opt._file}' is already declared in ${showFiles res.declarations}."
284 else
285 let
286 /* Add the modules of the current option to the list of modules
···289 current option declaration as the file use for the submodule. If the
290 submodule defines any filename, then we ignore the enclosing option file. */
291 options' = toList opt.options.options;
292+00293 getSubModules = opt.options.type.getSubModules or null;
294 submodules =
295+ if getSubModules != null then map (packSubmodule opt._file) getSubModules ++ res.options
296+ else if opt.options ? options then map (coerceOption opt._file) options' ++ res.options
297 else res.options;
298 in opt.options // res //
299+ { declarations = res.declarations ++ [opt._file];
300 options = submodules;
301 } // typeSet
302 ) { inherit loc; declarations = []; options = []; } opts;
···164checkConfigOutput "false" config.enable ./alias-with-priority-can-override.nix
165checkConfigOutput "false" config.enableAlias ./alias-with-priority-can-override.nix
166167+# submoduleWith
168+169+## specialArgs should work
170+checkConfigOutput "foo" config.submodule.foo ./declare-submoduleWith-special.nix
171+172+## shorthandOnlyDefines config behaves as expected
173+checkConfigOutput "true" config.submodule.config ./declare-submoduleWith-shorthand.nix ./define-submoduleWith-shorthand.nix
174+checkConfigError 'is not of type `boolean' config.submodule.config ./declare-submoduleWith-shorthand.nix ./define-submoduleWith-noshorthand.nix
175+checkConfigError 'value is a boolean while a set was expected' config.submodule.config ./declare-submoduleWith-noshorthand.nix ./define-submoduleWith-shorthand.nix
176+checkConfigOutput "true" config.submodule.config ./declare-submoduleWith-noshorthand.nix ./define-submoduleWith-noshorthand.nix
177+178+## submoduleWith should merge all modules in one swoop
179+checkConfigOutput "true" config.submodule.inner ./declare-submoduleWith-modules.nix
180+checkConfigOutput "true" config.submodule.outer ./declare-submoduleWith-modules.nix
181+182+## Paths should be allowed as values and work as expected
183+checkConfigOutput "true" config.submodule.enable ./declare-submoduleWith-path.nix
184+185cat <<EOF
186====== module tests ======
187$pass Pass
···358 };
359360 # A submodule (like typed attribute set). See NixOS manual.
361- submodule = opts:
000000000362 let
363- opts' = toList opts;
364 inherit (lib.modules) evalModules;
000000000000365 in
366 mkOptionType rec {
367 name = "submodule";
368- check = x: isAttrs x || isFunction x;
369 merge = loc: defs:
370- let
371- coerce = def: if isFunction def then def else { config = def; };
372- modules = opts' ++ map (def: { _file = def.file; imports = [(coerce def.value)]; }) defs;
373- in (evalModules {
374- inherit modules;
375 args.name = last loc;
376 prefix = loc;
377 }).config;
378 getSubOptions = prefix: (evalModules
379- { modules = opts'; inherit prefix;
380 # This is a work-around due to the fact that some sub-modules,
381 # such as the one included in an attribute set, expects a "args"
382 # attribute to be given to the sub-module. As the option
···394 # It shouldn't cause an issue since this is cosmetic for the manual.
395 args.name = "‹name›";
396 }).options;
397- getSubModules = opts';
398- substSubModules = m: submodule m;
399- functor = (defaultFunctor name) // {
400- # Merging of submodules is done as part of mergeOptionDecls, as we have to annotate
401- # each submodule with its location.
402- payload = [];
403- binOp = lhs: rhs: [];
0000000000000000404 };
405 };
406
···358 };
359360 # A submodule (like typed attribute set). See NixOS manual.
361+ submodule = modules: submoduleWith {
362+ shorthandOnlyDefinesConfig = true;
363+ modules = toList modules;
364+ };
365+366+ submoduleWith =
367+ { modules
368+ , specialArgs ? {}
369+ , shorthandOnlyDefinesConfig ? false
370+ }@attrs:
371 let
0372 inherit (lib.modules) evalModules;
373+374+ coerce = unify: value: if isFunction value
375+ then setFunctionArgs (args: unify (value args)) (functionArgs value)
376+ else unify (if shorthandOnlyDefinesConfig then { config = value; } else value);
377+378+ allModules = defs: modules ++ imap1 (n: { value, file }:
379+ if isAttrs value || isFunction value then
380+ # Annotate the value with the location of its definition for better error messages
381+ coerce (lib.modules.unifyModuleSyntax file "${toString file}-${toString n}") value
382+ else value
383+ ) defs;
384+385 in
386 mkOptionType rec {
387 name = "submodule";
388+ check = x: isAttrs x || isFunction x || path.check x;
389 merge = loc: defs:
390+ (evalModules {
391+ modules = allModules defs;
392+ inherit specialArgs;
00393 args.name = last loc;
394 prefix = loc;
395 }).config;
396 getSubOptions = prefix: (evalModules
397+ { inherit modules prefix specialArgs;
398 # This is a work-around due to the fact that some sub-modules,
399 # such as the one included in an attribute set, expects a "args"
400 # attribute to be given to the sub-module. As the option
···412 # It shouldn't cause an issue since this is cosmetic for the manual.
413 args.name = "‹name›";
414 }).options;
415+ getSubModules = modules;
416+ substSubModules = m: submoduleWith (attrs // {
417+ modules = m;
418+ });
419+ functor = defaultFunctor name // {
420+ type = types.submoduleWith;
421+ payload = {
422+ modules = modules;
423+ specialArgs = specialArgs;
424+ shorthandOnlyDefinesConfig = shorthandOnlyDefinesConfig;
425+ };
426+ binOp = lhs: rhs: {
427+ modules = lhs.modules ++ rhs.modules;
428+ specialArgs =
429+ let intersecting = builtins.intersectAttrs lhs.specialArgs rhs.specialArgs;
430+ in if intersecting == {}
431+ then lhs.specialArgs // rhs.specialArgs
432+ else throw "A submoduleWith option is declared multiple times with the same specialArgs \"${toString (attrNames intersecting)}\"";
433+ shorthandOnlyDefinesConfig =
434+ if lhs.shorthandOnlyDefinesConfig == rhs.shorthandOnlyDefinesConfig
435+ then lhs.shorthandOnlyDefinesConfig
436+ else throw "A submoduleWith option is declared multiple times with conflicting shorthandOnlyDefinesConfig values";
437+ };
438 };
439 };
440
···257 <listitem>
258 <para>
259 A set of sub options <replaceable>o</replaceable>.
260- <replaceable>o</replaceable> can be an attribute set or a function
261- returning an attribute set. Submodules are used in composed types to
262- create modular options. Submodule are detailed in
00263 <xref
264 linkend='section-option-types-submodule' />.
265 </para>
266 </listitem>
0000000000000000000000000000000000000000000000000000267 </varlistentry>
268 </variablelist>
269 </section>
···257 <listitem>
258 <para>
259 A set of sub options <replaceable>o</replaceable>.
260+ <replaceable>o</replaceable> can be an attribute set, a function
261+ returning an attribute set, or a path to a file containing such a value. Submodules are used in
262+ composed types to create modular options. This is equivalent to
263+ <literal>types.submoduleWith { modules = toList o; shorthandOnlyDefinesConfig = true; }</literal>.
264+ Submodules are detailed in
265 <xref
266 linkend='section-option-types-submodule' />.
267 </para>
268 </listitem>
269+ </varlistentry>
270+ <varlistentry>
271+ <term>
272+ <varname>types.submoduleWith</varname> {
273+ <replaceable>modules</replaceable>,
274+ <replaceable>specialArgs</replaceable> ? {},
275+ <replaceable>shorthandOnlyDefinesConfig</replaceable> ? false }
276+ </term>
277+ <listitem>
278+ <para>
279+ Like <varname>types.submodule</varname>, but more flexible and with better defaults.
280+ It has parameters
281+ <itemizedlist>
282+ <listitem><para>
283+ <replaceable>modules</replaceable>
284+ A list of modules to use by default for this submodule type. This gets combined
285+ with all option definitions to build the final list of modules that will be included.
286+ <note><para>
287+ Only options defined with this argument are included in rendered documentation.
288+ </para></note>
289+ </para></listitem>
290+ <listitem><para>
291+ <replaceable>specialArgs</replaceable>
292+ An attribute set of extra arguments to be passed to the module functions.
293+ The option <literal>_module.args</literal> should be used instead
294+ for most arguments since it allows overriding. <replaceable>specialArgs</replaceable> should only be
295+ used for arguments that can't go through the module fixed-point, because of
296+ infinite recursion or other problems. An example is overriding the
297+ <varname>lib</varname> argument, because <varname>lib</varname> itself is used
298+ to define <literal>_module.args</literal>, which makes using
299+ <literal>_module.args</literal> to define it impossible.
300+ </para></listitem>
301+ <listitem><para>
302+ <replaceable>shorthandOnlyDefinesConfig</replaceable>
303+ Whether definitions of this type should default to the <literal>config</literal>
304+ section of a module (see <xref linkend='ex-module-syntax'/>) if it is an attribute
305+ set. Enabling this only has a benefit when the submodule defines an option named
306+ <literal>config</literal> or <literal>options</literal>. In such a case it would
307+ allow the option to be set with <literal>the-submodule.config = "value"</literal>
308+ instead of requiring <literal>the-submodule.config.config = "value"</literal>.
309+ This is because only when modules <emphasis>don't</emphasis> set the
310+ <literal>config</literal> or <literal>options</literal> keys, all keys are interpreted
311+ as option definitions in the <literal>config</literal> section. Enabling this option
312+ implicitly puts all attributes in the <literal>config</literal> section.
313+ </para>
314+ <para>
315+ With this option enabled, defining a non-<literal>config</literal> section requires
316+ using a function: <literal>the-submodule = { ... }: { options = { ... }; }</literal>.
317+ </para></listitem>
318+ </itemizedlist>
319+ </para>
320+ </listitem>
321 </varlistentry>
322 </variablelist>
323 </section>
···2728 {
29 users.users.testuser = { };
30+ users.users.testuser2 = { };
31 services.mysql.enable = true;
32 services.mysql.initialScript = pkgs.writeText "mariadb-init.sql" ''
33 ALTER USER root@localhost IDENTIFIED WITH unix_socket;
···35 DELETE FROM mysql.user WHERE user = ''';
36 FLUSH PRIVILEGES;
37 '';
38+ services.mysql.ensureDatabases = [ "testdb" "testdb2" ];
39 services.mysql.ensureUsers = [{
40 name = "testuser";
41 ensurePermissions = {
42 "testdb.*" = "ALL PRIVILEGES";
43 };
44+ } {
45+ name = "testuser2";
46+ ensurePermissions = {
47+ "testdb2.*" = "ALL PRIVILEGES";
48+ };
49 }];
50 services.mysql.package = pkgs.mariadb;
51 };
···53 };
5455 testScript = ''
56+ start_all()
5758 mysql.wait_for_unit("mysql")
59 mysql.succeed("echo 'use empty_testdb;' | mysql -u root")
···67 )
68 mariadb.succeed(
69 "echo 'use testdb; insert into tests values (42);' | sudo -u testuser mysql -u testuser"
70+ )
71+ # Ensure testuser2 is not able to insert into testdb as mysql testuser2
72+ mariadb.fail(
73+ "echo 'use testdb; insert into tests values (23);' | sudo -u testuser2 mysql -u testuser2"
74+ )
75+ # Ensure testuser2 is not able to authenticate as mysql testuser
76+ mariadb.fail(
77+ "echo 'use testdb; insert into tests values (23);' | sudo -u testuser2 mysql -u testuser"
78 )
79 mariadb.succeed(
80 "echo 'use testdb; select test_id from tests;' | sudo -u testuser mysql -u testuser -N | grep 42"
+1
nixos/tests/netdata.nix
···2526 # check if the netdata main page loads.
27 netdata.succeed("curl --fail http://localhost:19999/")
02829 # check if netdata can read disk ops for root owned processes.
30 # if > 0, successful. verifies both netdata working and
···2526 # check if the netdata main page loads.
27 netdata.succeed("curl --fail http://localhost:19999/")
28+ netdata.succeed("sleep 4")
2930 # check if netdata can read disk ops for root owned processes.
31 # if > 0, successful. verifies both netdata working and
···12 pname = "gotify-server";
13 # Note that when this is updated, along with the hash, the `ui.nix` file
14 # should include the same changes to the version and the sha256.
15- version = "2.0.12";
1617 src = fetchFromGitHub {
18 owner = "gotify";
19 repo = "server";
20 rev = "v${version}";
21- sha256 = "0pkws83ymmlxcdxadb1w6rmibw84vzhx9xrhxc6b1rjncb80l0kk";
22 };
2324 modSha256 = "1awhbc8qs2bwv6y2vwd92r4ys0l1bzymrb36iamr040x961682wv";
···12 pname = "gotify-server";
13 # Note that when this is updated, along with the hash, the `ui.nix` file
14 # should include the same changes to the version and the sha256.
15+ version = "2.0.13";
1617 src = fetchFromGitHub {
18 owner = "gotify";
19 repo = "server";
20 rev = "v${version}";
21+ sha256 = "11ycs1ci1z8wm4fjgk4454kgszr4s8q9dc96pl77yvlngi4dk46d";
22 };
2324 modSha256 = "1awhbc8qs2bwv6y2vwd92r4ys0l1bzymrb36iamr040x961682wv";