Merge pull request #81848 from grahamc/nested-specialisation

specialisation: replace nesting with named configurations
This commit is contained in:
Graham Christensen 2020-04-12 08:56:11 -04:00 committed by GitHub
commit 35d8514a91
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 230 additions and 189 deletions

View file

@ -40,7 +40,7 @@ networking.proxy.noProxy = "127.0.0.1,localhost,internal.domain";
<note>
<para>
If you are switching networks with different proxy configurations, use the
<literal>nesting.clone</literal> option in
<literal>specialisation</literal> option in
<literal>configuration.nix</literal> to switch proxies at runtime. Refer to
<xref linkend="ch-options" /> for more information.
</para>

View file

@ -203,6 +203,50 @@ environment.systemPackages = [
<link xlink:href="https://github.com/gollum/gollum/wiki/5.0-release-notes#migrating-your-wiki">here</link>.
</para>
</listitem>
<listitem>
<para>
The NixOS options <literal>nesting.clone</literal> and
<literal>nesting.children</literal> have been deleted, and
replaced with named <xref linkend="opt-specialisation"/>
configurations.
</para>
<para>
Replace a <literal>nesting.clone</literal> entry with:
<programlisting>{
<link xlink:href="#opt-specialisation">specialisation.example-sub-configuration</link> = {
<link xlink:href="#opt-specialisation._name_.configuration">configuration</link> = {
...
};
};</programlisting>
</para>
<para>
Replace a <literal>nesting.children</literal> entry with:
<programlisting>{
<link xlink:href="#opt-specialisation">specialisation.example-sub-configuration</link> = {
<link xlink:href="#opt-specialisation._name_.inheritParentConfig">inheritParentConfig</link> = false;
<link xlink:href="#opt-specialisation._name_.configuration">configuration</link> = {
...
};
};</programlisting>
</para>
<para>
To switch to a specialised configuration at runtime you need to
run:
<programlisting>
# sudo /run/current-system/specialisation/example-sub-configuration/bin/switch-to-configuration test
</programlisting>
Before you would have used:
<programlisting>
# sudo /run/current-system/fine-tune/child-1/bin/switch-to-configuration test
</programlisting>
</para>
</listitem>
</itemizedlist>
</section>

View file

@ -4,6 +4,5 @@ with lib;
{
boot.loader.grub.device = mkOverride 0 "nodev";
nesting.children = mkOverride 0 [];
nesting.clone = mkOverride 0 [];
specialisation = mkOverride 0 {};
}

View file

@ -11,21 +11,16 @@ let
# you can provide an easy way to boot the same configuration
# as you use, but with another kernel
# !!! fix this
cloner = inheritParent: list:
map (childConfig:
children = mapAttrs (childName: childConfig:
(import ../../../lib/eval-config.nix {
inherit baseModules;
system = config.nixpkgs.initialSystem;
modules =
(optionals inheritParent modules)
(optionals childConfig.inheritParentConfig modules)
++ [ ./no-clone.nix ]
++ [ childConfig ];
++ [ childConfig.configuration ];
}).config.system.build.toplevel
) list;
children =
cloner false config.nesting.children
++ cloner true config.nesting.clone;
) config.specialisation;
systemBuilder =
let
@ -77,12 +72,9 @@ let
echo -n "$nixosLabel" > $out/nixos-version
echo -n "${config.boot.kernelPackages.stdenv.hostPlatform.system}" > $out/system
mkdir $out/fine-tune
childCount=0
for i in $children; do
childCount=$(( childCount + 1 ))
ln -s $i $out/fine-tune/child-$childCount
done
mkdir $out/specialisation
${concatStringsSep "\n"
(mapAttrsToList (name: path: "ln -s ${path} $out/specialisation/${name}") children)}
mkdir $out/bin
export localeArchive="${config.i18n.glibcLocales}/lib/locale/locale-archive"
@ -112,7 +104,6 @@ let
shell = "${pkgs.bash}/bin/sh";
su = "${pkgs.shadow.su}/bin/su";
inherit children;
kernelParams = config.boot.kernelParams;
installBootLoader =
config.system.build.installBootLoader
@ -143,6 +134,11 @@ let
in
{
imports = [
(mkRemovedOptionModule [ "nesting" "clone" ] "Use `specialisation.«name» = { inheritParentConfig = true; configuration = { ... }; }` instead.")
(mkRemovedOptionModule [ "nesting" "children" ] "Use `specialisation.«name».configuration = { ... }` instead.")
];
options = {
system.build = mkOption {
@ -154,26 +150,35 @@ in
'';
};
nesting.children = mkOption {
default = [];
specialisation = mkOption {
default = {};
example = lib.literalExample "{ fewJobsManyCores.configuration = { nix.buildCores = 0; nix.maxJobs = 1; }; }";
description = ''
Additional configurations to build.
'';
};
Additional configurations to build. If
<literal>inheritParentConfig</literal> is true, the system
will be based on the overall system configuration.
nesting.clone = mkOption {
default = [];
description = ''
Additional configurations to build based on the current
configuration which then has a lower priority.
To switch to a cloned configuration (e.g. <literal>child-1</literal>)
at runtime, run
To switch to a specialised configuration
(e.g. <literal>fewJobsManyCores</literal>) at runtime, run:
<programlisting>
# sudo /run/current-system/fine-tune/child-1/bin/switch-to-configuration test
# sudo /run/current-system/specialisation/fewJobsManyCores/bin/switch-to-configuration test
</programlisting>
'';
type = types.attrsOf (types.submodule (
{ ... }: {
options.inheritParentConfig = mkOption {
type = types.bool;
default = true;
description = "Include the entire system's configuration. Set to false to make a completely differently configured system.";
};
options.configuration = mkOption {
default = {};
description = "Arbitrary NixOS configuration options.";
};
})
);
};
system.boot.loader.id = mkOption {

View file

@ -409,7 +409,7 @@ $conf .= "$extraEntries\n" unless $extraEntriesBeforeNixOS;
# Find all the children of the current default configuration
# Do not search for grand children
my @links = sort (glob "$defaultConfig/fine-tune/*");
my @links = sort (glob "$defaultConfig/specialisation/*");
foreach my $link (@links) {
my $entryName = "";
@ -425,7 +425,8 @@ foreach my $link (@links) {
if ($cfgName) {
$entryName = $cfgName;
} else {
$entryName = "($date - $version)";
my $linkname = basename($link);
$entryName = "($linkname - $date - $version)";
}
addEntry("NixOS - $entryName", $link);
}

View file

@ -69,7 +69,7 @@ addEntry "NixOS - Default" $defaultConfig ""
# Add all generations of the system profile to the menu, in reverse
# (most recent to least recent) order.
for link in $((ls -d $defaultConfig/fine-tune/* ) | sort -n); do
for link in $((ls -d $defaultConfig/specialisation/* ) | sort -n); do
date=$(stat --printf="%y\n" $link | sed 's/\..*//')
addEntry "NixOS - variation" $link ""
done

View file

@ -91,52 +91,50 @@ in import ./make-test-python.nix {
security.acme.server = "https://acme-v02.api.letsencrypt.org/dir";
nesting.clone = [
({pkgs, ...}: {
systemd.targets."acme-finished-b.example.com" = {};
systemd.services."acme-b.example.com" = {
wants = [ "acme-finished-b.example.com.target" ];
before = [ "acme-finished-b.example.com.target" ];
after = [ "nginx.service" ];
};
services.nginx.virtualHosts."b.example.com" = {
enableACME = true;
forceSSL = true;
locations."/".root = pkgs.runCommand "docroot" {} ''
mkdir -p "$out"
echo hello world > "$out/index.html"
'';
};
})
({pkgs, config, nodes, lib, ...}: {
security.acme.certs."example.com" = {
domain = "*.example.com";
dnsProvider = "exec";
dnsPropagationCheck = false;
credentialsFile = with pkgs; writeText "wildcard.env" ''
EXEC_PATH=${dnsScript { inherit writeScript bash curl; dnsAddress = nodes.dnsserver.config.networking.primaryIPAddress; }}
'';
user = config.services.nginx.user;
group = config.services.nginx.group;
};
systemd.targets."acme-finished-example.com" = {};
systemd.services."acme-example.com" = {
wants = [ "acme-finished-example.com.target" ];
before = [ "acme-finished-example.com.target" "nginx.service" ];
wantedBy = [ "nginx.service" ];
};
services.nginx.virtualHosts."c.example.com" = {
forceSSL = true;
sslCertificate = config.security.acme.certs."example.com".directory + "/cert.pem";
sslTrustedCertificate = config.security.acme.certs."example.com".directory + "/full.pem";
sslCertificateKey = config.security.acme.certs."example.com".directory + "/key.pem";
locations."/".root = pkgs.runCommand "docroot" {} ''
mkdir -p "$out"
echo hello world > "$out/index.html"
'';
};
})
];
specialisation.second-cert.configuration = {pkgs, ...}: {
systemd.targets."acme-finished-b.example.com" = {};
systemd.services."acme-b.example.com" = {
wants = [ "acme-finished-b.example.com.target" ];
before = [ "acme-finished-b.example.com.target" ];
after = [ "nginx.service" ];
};
services.nginx.virtualHosts."b.example.com" = {
enableACME = true;
forceSSL = true;
locations."/".root = pkgs.runCommand "docroot" {} ''
mkdir -p "$out"
echo hello world > "$out/index.html"
'';
};
};
specialisation.dns-01.configuration = {pkgs, config, nodes, lib, ...}: {
security.acme.certs."example.com" = {
domain = "*.example.com";
dnsProvider = "exec";
dnsPropagationCheck = false;
credentialsFile = with pkgs; writeText "wildcard.env" ''
EXEC_PATH=${dnsScript { inherit writeScript bash curl; dnsAddress = nodes.dnsserver.config.networking.primaryIPAddress; }}
'';
user = config.services.nginx.user;
group = config.services.nginx.group;
};
systemd.targets."acme-finished-example.com" = {};
systemd.services."acme-example.com" = {
wants = [ "acme-finished-example.com.target" ];
before = [ "acme-finished-example.com.target" "nginx.service" ];
wantedBy = [ "nginx.service" ];
};
services.nginx.virtualHosts."c.example.com" = {
forceSSL = true;
sslCertificate = config.security.acme.certs."example.com".directory + "/cert.pem";
sslTrustedCertificate = config.security.acme.certs."example.com".directory + "/full.pem";
sslCertificateKey = config.security.acme.certs."example.com".directory + "/key.pem";
locations."/".root = pkgs.runCommand "docroot" {} ''
mkdir -p "$out"
echo hello world > "$out/index.html"
'';
};
};
};
client = {nodes, lib, ...}: {
@ -196,7 +194,7 @@ in import ./make-test-python.nix {
with subtest("Can add another certificate for nginx service"):
webserver.succeed(
"/run/current-system/fine-tune/child-1/bin/switch-to-configuration test"
"/run/current-system/specialisation/second-cert/bin/switch-to-configuration test"
)
webserver.wait_for_unit("acme-finished-b.example.com.target")
client.succeed(
@ -208,7 +206,7 @@ in import ./make-test-python.nix {
"${switchToNewServer}"
)
webserver.succeed(
"/run/current-system/fine-tune/child-2/bin/switch-to-configuration test"
"/run/current-system/specialisation/dns-01/bin/switch-to-configuration test"
)
webserver.wait_for_unit("acme-finished-example.com.target")
client.succeed(

View file

@ -202,7 +202,7 @@ in
nat.standalone = handleTest ./nat.nix { withFirewall = false; };
ndppd = handleTest ./ndppd.nix {};
neo4j = handleTest ./neo4j.nix {};
nesting = handleTest ./nesting.nix {};
specialisation = handleTest ./specialisation.nix {};
netdata = handleTest ./netdata.nix {};
networking.networkd = handleTest ./networking.nix { networkd = true; };
networking.scripted = handleTest ./networking.nix { networkd = false; };

View file

@ -20,35 +20,33 @@ import ./make-test-python.nix ({ pkgs, ... }: {
}
'';
nesting.clone = [
{
services.caddy.config = lib.mkForce ''
http://localhost {
gzip
specialisation.etag.configuration = {
services.caddy.config = lib.mkForce ''
http://localhost {
gzip
root ${
pkgs.runCommand "testdir2" {} ''
mkdir "$out"
echo changed > "$out/example.html"
''
}
root ${
pkgs.runCommand "testdir2" {} ''
mkdir "$out"
echo changed > "$out/example.html"
''
}
'';
}
}
'';
};
{
services.caddy.config = ''
http://localhost:8080 {
}
'';
}
];
specialisation.config-reload.configuration = {
services.caddy.config = ''
http://localhost:8080 {
}
'';
};
};
};
testScript = { nodes, ... }: let
etagSystem = "${nodes.webserver.config.system.build.toplevel}/fine-tune/child-1";
justReloadSystem = "${nodes.webserver.config.system.build.toplevel}/fine-tune/child-2";
etagSystem = "${nodes.webserver.config.system.build.toplevel}/specialisation/etag";
justReloadSystem = "${nodes.webserver.config.system.build.toplevel}/specialisation/config-reload";
in ''
url = "http://localhost/example.html"
webserver.wait_for_unit("caddy")
@ -77,7 +75,7 @@ import ./make-test-python.nix ({ pkgs, ... }: {
assert old_etag != new_etag, "Old ETag {} is the same as {}".format(
old_etag, new_etag
)
with subtest("config is reloaded on nixos-rebuild switch"):
webserver.succeed(
"${justReloadSystem}/bin/switch-to-configuration test >&2"

View file

@ -65,7 +65,7 @@ let
# partitions and filesystems.
testScriptFun = { bootLoader, createPartitions, grubVersion, grubDevice, grubUseEfi
, grubIdentifier, preBootCommands, extraConfig
, testCloneConfig
, testSpecialisationConfig
}:
let iface = if grubVersion == 1 then "ide" else "virtio";
isEfi = bootLoader == "systemd-boot" || (bootLoader == "grub" && grubUseEfi);
@ -220,7 +220,7 @@ let
# Tests for validating clone configuration entries in grub menu
''
+ optionalString testCloneConfig ''
+ optionalString testSpecialisationConfig ''
# Reboot Machine
machine = create_machine_named("clone-default-config")
${preBootCommands}
@ -262,7 +262,7 @@ let
, bootLoader ? "grub" # either "grub" or "systemd-boot"
, grubVersion ? 2, grubDevice ? "/dev/vda", grubIdentifier ? "uuid", grubUseEfi ? false
, enableOCR ? false, meta ? {}
, testCloneConfig ? false
, testSpecialisationConfig ? false
}:
makeTest {
inherit enableOCR;
@ -337,7 +337,7 @@ let
testScript = testScriptFun {
inherit bootLoader createPartitions preBootCommands
grubVersion grubDevice grubIdentifier grubUseEfi extraConfig
testCloneConfig;
testSpecialisationConfig;
};
};
@ -411,11 +411,11 @@ let
grubUseEfi = true;
};
clone-test-extraconfig = {
specialisation-test-extraconfig = {
extraConfig = ''
environment.systemPackages = [ pkgs.grub2 ];
boot.loader.grub.configurationName = "Home";
nesting.clone = [ {
specialisation.work.configuration = {
boot.loader.grub.configurationName = lib.mkForce "Work";
environment.etc = {
@ -424,9 +424,9 @@ let
gitproxy = none for work.com
";
};
} ];
};
'';
testCloneConfig = true;
testSpecialisationConfig = true;
};
@ -440,7 +440,7 @@ in {
simple = makeInstallerTest "simple" simple-test-config;
# Test cloned configurations with the simple grub configuration
simpleClone = makeInstallerTest "simpleClone" (simple-test-config // clone-test-extraconfig);
simpleSpecialised = makeInstallerTest "simpleSpecialised" (simple-test-config // specialisation-test-extraconfig);
# Simple GPT/UEFI configuration using systemd-boot with 3 partitions: ESP, swap & root filesystem
simpleUefiSystemdBoot = makeInstallerTest "simpleUefiSystemdBoot" {
@ -467,7 +467,7 @@ in {
simpleUefiGrub = makeInstallerTest "simpleUefiGrub" simple-uefi-grub-config;
# Test cloned configurations with the uefi grub configuration
simpleUefiGrubClone = makeInstallerTest "simpleUefiGrubClone" (simple-uefi-grub-config // clone-test-extraconfig);
simpleUefiGrubSpecialisation = makeInstallerTest "simpleUefiGrubSpecialisation" (simple-uefi-grub-config // specialisation-test-extraconfig);
# Same as the previous, but now with a separate /boot partition.
separateBoot = makeInstallerTest "separateBoot" {

View file

@ -1,44 +0,0 @@
import ./make-test-python.nix {
name = "nesting";
nodes = {
clone = { pkgs, ... }: {
environment.systemPackages = [ pkgs.cowsay ];
nesting.clone = [
({ pkgs, ... }: {
environment.systemPackages = [ pkgs.hello ];
})
];
};
children = { pkgs, ... }: {
environment.systemPackages = [ pkgs.cowsay ];
nesting.children = [
({ pkgs, ... }: {
environment.systemPackages = [ pkgs.hello ];
})
];
};
};
testScript = ''
clone.wait_for_unit("default.target")
clone.succeed("cowsay hey")
clone.fail("hello")
with subtest("Nested clones do inherit from parent"):
clone.succeed(
"/run/current-system/fine-tune/child-1/bin/switch-to-configuration test"
)
clone.succeed("cowsay hey")
clone.succeed("hello")
children.wait_for_unit("default.target")
children.succeed("cowsay hey")
children.fail("hello")
with subtest("Nested children do not inherit from parent"):
children.succeed(
"/run/current-system/fine-tune/child-1/bin/switch-to-configuration test"
)
children.fail("cowsay hey")
children.succeed("hello")
'';
}

View file

@ -19,7 +19,7 @@ import ./make-test-python.nix {
'';
};
nesting.clone = lib.singleton {
specialisation.pass-checks.configuration = {
services.nginx.virtualHosts.server = {
root = lib.mkForce (pkgs.runCommandLocal "testdir2" {} ''
mkdir "$out"
@ -70,7 +70,7 @@ import ./make-test-python.nix {
testScript = { nodes, ... }: let
inherit (nodes.server.config.system.build) toplevel;
newSystem = "${toplevel}/fine-tune/child-1";
newSystem = "${toplevel}/specialisation/pass-checks";
in ''
start_all()

View file

@ -42,38 +42,35 @@ import ./make-test-python.nix ({ pkgs, ... }: {
services.nginx.enableReload = true;
nesting.clone = [
{
services.nginx.virtualHosts.localhost = {
root = lib.mkForce (pkgs.runCommand "testdir2" {} ''
mkdir "$out"
echo content changed > "$out/index.html"
'');
};
}
specialisation.etagSystem.configuration = {
services.nginx.virtualHosts.localhost = {
root = lib.mkForce (pkgs.runCommand "testdir2" {} ''
mkdir "$out"
echo content changed > "$out/index.html"
'');
};
};
{
services.nginx.virtualHosts."1.my.test".listen = [ { addr = "127.0.0.1"; port = 8080; }];
}
specialisation.justReloadSystem.configuration = {
services.nginx.virtualHosts."1.my.test".listen = [ { addr = "127.0.0.1"; port = 8080; }];
};
{
services.nginx.package = pkgs.nginxUnstable;
}
specialisation.reloadRestartSystem.configuration = {
services.nginx.package = pkgs.nginxUnstable;
};
{
services.nginx.package = pkgs.nginxUnstable;
services.nginx.virtualHosts."!@$$(#*%".locations."~@#*$*!)".proxyPass = ";;;";
}
];
specialisation.reloadWithErrorsSystem.configuration = {
services.nginx.package = pkgs.nginxUnstable;
services.nginx.virtualHosts."!@$$(#*%".locations."~@#*$*!)".proxyPass = ";;;";
};
};
};
testScript = { nodes, ... }: let
etagSystem = "${nodes.webserver.config.system.build.toplevel}/fine-tune/child-1";
justReloadSystem = "${nodes.webserver.config.system.build.toplevel}/fine-tune/child-2";
reloadRestartSystem = "${nodes.webserver.config.system.build.toplevel}/fine-tune/child-3";
reloadWithErrorsSystem = "${nodes.webserver.config.system.build.toplevel}/fine-tune/child-4";
etagSystem = "${nodes.webserver.config.system.build.toplevel}/specialisation/etagSystem";
justReloadSystem = "${nodes.webserver.config.system.build.toplevel}/specialisation/justReloadSystem";
reloadRestartSystem = "${nodes.webserver.config.system.build.toplevel}/specialisation/reloadRestartSystem";
reloadWithErrorsSystem = "${nodes.webserver.config.system.build.toplevel}/specialisation/reloadWithErrorsSystem";
in ''
url = "http://localhost/index.html"

View file

@ -0,0 +1,43 @@
import ./make-test-python.nix {
name = "specialisation";
nodes = {
inheritconf = { pkgs, ... }: {
environment.systemPackages = [ pkgs.cowsay ];
specialisation.inheritconf.configuration = { pkgs, ... }: {
environment.systemPackages = [ pkgs.hello ];
};
};
noinheritconf = { pkgs, ... }: {
environment.systemPackages = [ pkgs.cowsay ];
specialisation.noinheritconf = {
inheritParentConfig = false;
configuration = { pkgs, ... }: {
environment.systemPackages = [ pkgs.hello ];
};
};
};
};
testScript = ''
inheritconf.wait_for_unit("default.target")
inheritconf.succeed("cowsay hey")
inheritconf.fail("hello")
with subtest("Nested clones do inherit from parent"):
inheritconf.succeed(
"/run/current-system/specialisation/inheritconf/bin/switch-to-configuration test"
)
inheritconf.succeed("cowsay hey")
inheritconf.succeed("hello")
noinheritconf.wait_for_unit("default.target")
noinheritconf.succeed("cowsay hey")
noinheritconf.fail("hello")
with subtest("Nested children do not inherit from parent"):
noinheritconf.succeed(
"/run/current-system/specialisation/noinheritconf/bin/switch-to-configuration test"
)
noinheritconf.fail("cowsay hey")
noinheritconf.succeed("hello")
'';
}