make config with secrets correctly generated
This commit is contained in:
parent
dc46ec8eda
commit
8ec12338fd
7 changed files with 214 additions and 78 deletions
101
lib/default.nix
101
lib/default.nix
|
@ -1,10 +1,15 @@
|
||||||
{ pkgs, lib }:
|
{ pkgs, lib }:
|
||||||
rec {
|
rec {
|
||||||
|
# Replace secrets in a file.
|
||||||
|
# - userConfig is an attrset that will produce a config file.
|
||||||
|
# - resultPath is the location the config file should have on the filesystem.
|
||||||
|
# - generator is a function taking two arguments name and value and returning path in the nix
|
||||||
|
# nix store where the
|
||||||
replaceSecrets = { userConfig, resultPath, generator }:
|
replaceSecrets = { userConfig, resultPath, generator }:
|
||||||
let
|
let
|
||||||
configWithTemplates = withReplacements userConfig;
|
configWithTemplates = withReplacements userConfig;
|
||||||
|
|
||||||
nonSecretConfigFile = pkgs.writeText "${resultPath}.template" (generator "template" configWithTemplates);
|
nonSecretConfigFile = generator "template" configWithTemplates;
|
||||||
|
|
||||||
replacements = getReplacements userConfig;
|
replacements = getReplacements userConfig;
|
||||||
in
|
in
|
||||||
|
@ -13,6 +18,9 @@ rec {
|
||||||
inherit resultPath replacements;
|
inherit resultPath replacements;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
replaceSecretsFormatAdapter = format: format.generate;
|
||||||
|
replaceSecretsGeneratorAdapter = generator: name: value: pkgs.writeText "generator " (generator value);
|
||||||
|
|
||||||
template = file: newPath: replacements: replaceSecretsScript {
|
template = file: newPath: replacements: replaceSecretsScript {
|
||||||
inherit file replacements;
|
inherit file replacements;
|
||||||
resultPath = newPath;
|
resultPath = newPath;
|
||||||
|
@ -22,6 +30,9 @@ rec {
|
||||||
let
|
let
|
||||||
templatePath = resultPath + ".template";
|
templatePath = resultPath + ".template";
|
||||||
sedPatterns = lib.strings.concatStringsSep " " (lib.attrsets.mapAttrsToList (from: to: "-e \"s|${from}|${to}|\"") replacements);
|
sedPatterns = lib.strings.concatStringsSep " " (lib.attrsets.mapAttrsToList (from: to: "-e \"s|${from}|${to}|\"") replacements);
|
||||||
|
sedCmd = if replacements == {}
|
||||||
|
then "cat"
|
||||||
|
else "${pkgs.gnused}/bin/sed ${sedPatterns}";
|
||||||
in
|
in
|
||||||
''
|
''
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
@ -29,11 +40,7 @@ rec {
|
||||||
mkdir -p $(dirname ${templatePath})
|
mkdir -p $(dirname ${templatePath})
|
||||||
ln -fs ${file} ${templatePath}
|
ln -fs ${file} ${templatePath}
|
||||||
rm -f ${resultPath}
|
rm -f ${resultPath}
|
||||||
if [ -z "${sedPatterns}" ]; then
|
${sedCmd} ${templatePath} > ${resultPath}
|
||||||
cat ${templatePath} > ${resultPath}
|
|
||||||
else
|
|
||||||
${pkgs.gnused}/bin/sed ${sedPatterns} ${templatePath} > ${resultPath}
|
|
||||||
fi
|
|
||||||
'';
|
'';
|
||||||
|
|
||||||
secretFileType = lib.types.submodule {
|
secretFileType = lib.types.submodule {
|
||||||
|
@ -115,4 +122,86 @@ rec {
|
||||||
lib.lists.concatMap (collect pred) attrs
|
lib.lists.concatMap (collect pred) attrs
|
||||||
else
|
else
|
||||||
[];
|
[];
|
||||||
|
|
||||||
|
# Generator for XML
|
||||||
|
formatXML = {
|
||||||
|
enclosingRoot ? null
|
||||||
|
}: {
|
||||||
|
type = with lib.types; let
|
||||||
|
valueType = nullOr (oneOf [
|
||||||
|
bool
|
||||||
|
int
|
||||||
|
float
|
||||||
|
str
|
||||||
|
path
|
||||||
|
(attrsOf valueType)
|
||||||
|
(listOf valueType)
|
||||||
|
]) // {
|
||||||
|
description = "XML value";
|
||||||
|
};
|
||||||
|
in valueType;
|
||||||
|
|
||||||
|
generate = name: value: pkgs.callPackage ({ runCommand, python3 }: runCommand "config" {
|
||||||
|
value = builtins.toJSON (
|
||||||
|
if enclosingRoot == null then
|
||||||
|
value
|
||||||
|
else
|
||||||
|
{ ${enclosingRoot} = value; });
|
||||||
|
passAsFile = [ "value" ];
|
||||||
|
} (pkgs.writers.writePython3 "dict2xml" {
|
||||||
|
libraries = with python3.pkgs; [ python dict2xml ];
|
||||||
|
} ''
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
from dict2xml import dict2xml
|
||||||
|
|
||||||
|
with open(os.environ["valuePath"]) as f:
|
||||||
|
content = json.loads(f.read())
|
||||||
|
if content is None:
|
||||||
|
print("Could not parse env var valuePath as json")
|
||||||
|
os.exit(2)
|
||||||
|
with open(os.environ["out"], "w") as out:
|
||||||
|
out.write(dict2xml(content))
|
||||||
|
'')) {};
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
parseXML = xml:
|
||||||
|
let
|
||||||
|
xmlToJsonFile = pkgs.callPackage ({ runCommand, python3 }: runCommand "config" {
|
||||||
|
inherit xml;
|
||||||
|
passAsFile = [ "xml" ];
|
||||||
|
} (pkgs.writers.writePython3 "xml2json" {
|
||||||
|
libraries = with python3.pkgs; [ python ];
|
||||||
|
} ''
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
from collections import ChainMap
|
||||||
|
from xml.etree import ElementTree
|
||||||
|
|
||||||
|
|
||||||
|
def xml_to_dict_recursive(root):
|
||||||
|
all_descendants = list(root)
|
||||||
|
if len(all_descendants) == 0:
|
||||||
|
return {root.tag: root.text}
|
||||||
|
else:
|
||||||
|
merged_dict = ChainMap(*map(xml_to_dict_recursive, all_descendants))
|
||||||
|
return {root.tag: dict(merged_dict)}
|
||||||
|
|
||||||
|
|
||||||
|
with open(os.environ["xmlPath"]) as f:
|
||||||
|
root = ElementTree.XML(f.read())
|
||||||
|
xml = xml_to_dict_recursive(root)
|
||||||
|
j = json.dumps(xml)
|
||||||
|
|
||||||
|
with open(os.environ["out"], "w") as out:
|
||||||
|
out.write(j)
|
||||||
|
'')) {};
|
||||||
|
in
|
||||||
|
builtins.fromJSON (builtins.readFile xmlToJsonFile);
|
||||||
|
|
||||||
|
renameAttrName = attrset: from: to:
|
||||||
|
(lib.attrsets.filterAttrs (name: v: name == from) attrset) // {
|
||||||
|
${to} = attrset.${from};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -341,7 +341,7 @@ in
|
||||||
identity_providers.oidc.clients = clients;
|
identity_providers.oidc.clients = clients;
|
||||||
};
|
};
|
||||||
resultPath = "/var/lib/authelia-${fqdn}/oidc_clients.yaml";
|
resultPath = "/var/lib/authelia-${fqdn}/oidc_clients.yaml";
|
||||||
generator = name: value: lib.generators.toYAML {} value;
|
generator = shblib.replaceSecretsGeneratorAdapter (lib.generators.toYAML {});
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
lib.mkBefore (mkCfg cfg.oidcClients);
|
lib.mkBefore (mkCfg cfg.oidcClients);
|
||||||
|
|
|
@ -8,7 +8,7 @@ let
|
||||||
|
|
||||||
apps = {
|
apps = {
|
||||||
radarr = {
|
radarr = {
|
||||||
settingsFormat = formatXML {};
|
settingsFormat = shblib.formatXML { enclosingRoot = "Config"; };
|
||||||
moreOptions = {
|
moreOptions = {
|
||||||
settings = lib.mkOption {
|
settings = lib.mkOption {
|
||||||
description = "Specific options for radarr.";
|
description = "Specific options for radarr.";
|
||||||
|
@ -16,7 +16,7 @@ let
|
||||||
type = lib.types.submodule {
|
type = lib.types.submodule {
|
||||||
freeformType = apps.radarr.settingsFormat.type;
|
freeformType = apps.radarr.settingsFormat.type;
|
||||||
options = {
|
options = {
|
||||||
APIKey = lib.mkOption {
|
ApiKey = lib.mkOption {
|
||||||
type = shblib.secretFileType;
|
type = shblib.secretFileType;
|
||||||
description = "Path to api key secret file.";
|
description = "Path to api key secret file.";
|
||||||
};
|
};
|
||||||
|
@ -66,7 +66,7 @@ let
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
sonarr = {
|
sonarr = {
|
||||||
settingsFormat = formatXML {};
|
settingsFormat = shblib.formatXML { enclosingRoot = "Config"; };
|
||||||
moreOptions = {
|
moreOptions = {
|
||||||
settings = lib.mkOption {
|
settings = lib.mkOption {
|
||||||
description = "Specific options for sonarr.";
|
description = "Specific options for sonarr.";
|
||||||
|
@ -74,7 +74,7 @@ let
|
||||||
type = lib.types.submodule {
|
type = lib.types.submodule {
|
||||||
freeformType = apps.sonarr.settingsFormat.type;
|
freeformType = apps.sonarr.settingsFormat.type;
|
||||||
options = {
|
options = {
|
||||||
APIKey = lib.mkOption {
|
ApiKey = lib.mkOption {
|
||||||
type = shblib.secretFileType;
|
type = shblib.secretFileType;
|
||||||
description = "Path to api key secret file.";
|
description = "Path to api key secret file.";
|
||||||
};
|
};
|
||||||
|
@ -119,7 +119,7 @@ let
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
bazarr = {
|
bazarr = {
|
||||||
settingsFormat = formatXML {};
|
settingsFormat = shblib.formatXML { enclosingRoot = "Config"; };
|
||||||
moreOptions = {
|
moreOptions = {
|
||||||
settings = lib.mkOption {
|
settings = lib.mkOption {
|
||||||
description = "Specific options for bazarr.";
|
description = "Specific options for bazarr.";
|
||||||
|
@ -144,7 +144,7 @@ let
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
readarr = {
|
readarr = {
|
||||||
settingsFormat = formatXML {};
|
settingsFormat = shblib.formatXML { enclosingRoot = "Config"; };
|
||||||
moreOptions = {
|
moreOptions = {
|
||||||
settings = lib.mkOption {
|
settings = lib.mkOption {
|
||||||
description = "Specific options for readarr.";
|
description = "Specific options for readarr.";
|
||||||
|
@ -168,7 +168,7 @@ let
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
lidarr = {
|
lidarr = {
|
||||||
settingsFormat = formatXML {};
|
settingsFormat = shblib.formatXML { enclosingRoot = "Config"; };
|
||||||
moreOptions = {
|
moreOptions = {
|
||||||
settings = lib.mkOption {
|
settings = lib.mkOption {
|
||||||
description = "Specific options for lidarr.";
|
description = "Specific options for lidarr.";
|
||||||
|
@ -200,7 +200,7 @@ let
|
||||||
type = lib.types.submodule {
|
type = lib.types.submodule {
|
||||||
freeformType = apps.jackett.settingsFormat.type;
|
freeformType = apps.jackett.settingsFormat.type;
|
||||||
options = {
|
options = {
|
||||||
APIKey = lib.mkOption {
|
ApiKey = lib.mkOption {
|
||||||
type = shblib.secretFileType;
|
type = shblib.secretFileType;
|
||||||
description = "Path to api key secret file.";
|
description = "Path to api key secret file.";
|
||||||
};
|
};
|
||||||
|
@ -291,42 +291,6 @@ let
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
formatXML = {}: {
|
|
||||||
type = with lib.types; let
|
|
||||||
valueType = nullOr (oneOf [
|
|
||||||
bool
|
|
||||||
int
|
|
||||||
float
|
|
||||||
str
|
|
||||||
path
|
|
||||||
(attrsOf valueType)
|
|
||||||
(listOf valueType)
|
|
||||||
]) // {
|
|
||||||
description = "XML value";
|
|
||||||
};
|
|
||||||
in valueType;
|
|
||||||
|
|
||||||
generate = name: value: builtins.readFile (pkgs.callPackage ({ runCommand, python3 }: runCommand "config" {
|
|
||||||
value = builtins.toJSON {Config = value;};
|
|
||||||
passAsFile = [ "value" ];
|
|
||||||
} (pkgs.writers.writePython3 "dict2xml" {
|
|
||||||
libraries = with python3.pkgs; [ python dict2xml ];
|
|
||||||
} ''
|
|
||||||
import os
|
|
||||||
import json
|
|
||||||
from dict2xml import dict2xml
|
|
||||||
|
|
||||||
with open(os.environ["valuePath"]) as f:
|
|
||||||
content = json.loads(f.read())
|
|
||||||
if content is None:
|
|
||||||
print("Could not parse env var valuePath as json")
|
|
||||||
os.exit(2)
|
|
||||||
with open(os.environ["out"], "w") as out:
|
|
||||||
out.write(dict2xml(content))
|
|
||||||
'')) {});
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
appOption = name: c: lib.nameValuePair name (lib.mkOption {
|
appOption = name: c: lib.nameValuePair name (lib.mkOption {
|
||||||
description = "Configuration for ${name}";
|
description = "Configuration for ${name}";
|
||||||
default = {};
|
default = {};
|
||||||
|
@ -402,7 +366,7 @@ in
|
||||||
AuthenticationMethod = "External";
|
AuthenticationMethod = "External";
|
||||||
});
|
});
|
||||||
resultPath = "${config.services.radarr.dataDir}/config.xml";
|
resultPath = "${config.services.radarr.dataDir}/config.xml";
|
||||||
generator = apps.radarr.settingsFormat.generate;
|
generator = shblib.replaceSecretsFormatAdapter apps.radarr.settingsFormat;
|
||||||
};
|
};
|
||||||
|
|
||||||
shb.nginx.autheliaProtect = [ (autheliaProtect {} cfg') ];
|
shb.nginx.autheliaProtect = [ (autheliaProtect {} cfg') ];
|
||||||
|
@ -563,7 +527,7 @@ in
|
||||||
extraGroups = [ "media" ];
|
extraGroups = [ "media" ];
|
||||||
};
|
};
|
||||||
systemd.services.jackett.preStart = shblib.replaceSecrets {
|
systemd.services.jackett.preStart = shblib.replaceSecrets {
|
||||||
userConfig = cfg'.settings;
|
userConfig = shblib.renameAttrName cfg'.settings "ApiKey" "APIKey";
|
||||||
resultPath = "${config.services.jackett.dataDir}/ServerConfig.json";
|
resultPath = "${config.services.jackett.dataDir}/ServerConfig.json";
|
||||||
generator = apps.jackett.settingsFormat.generate;
|
generator = apps.jackett.settingsFormat.generate;
|
||||||
};
|
};
|
||||||
|
|
|
@ -126,7 +126,7 @@ in
|
||||||
enable = true;
|
enable = true;
|
||||||
authEndpoint = "https://oidc.example.com";
|
authEndpoint = "https://oidc.example.com";
|
||||||
settings = {
|
settings = {
|
||||||
APIKey.source = pkgs.writeText "key" "/run/radarr/apikey";
|
ApiKey.source = pkgs.writeText "key" "/run/radarr/apikey";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -199,7 +199,7 @@ in
|
||||||
enable = true;
|
enable = true;
|
||||||
authEndpoint = "https://oidc.example.com";
|
authEndpoint = "https://oidc.example.com";
|
||||||
settings = {
|
settings = {
|
||||||
APIKey.source = pkgs.writeText "key" "/run/radarr/apikey";
|
ApiKey.source = pkgs.writeText "key" "/run/radarr/apikey";
|
||||||
};
|
};
|
||||||
backupCfg = {
|
backupCfg = {
|
||||||
enable = true;
|
enable = true;
|
||||||
|
|
|
@ -107,4 +107,22 @@ in
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
testParseXML = {
|
||||||
|
expected = {
|
||||||
|
"a" = {
|
||||||
|
"b" = "1";
|
||||||
|
"c" = {
|
||||||
|
"d" = "1";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
expr = shblib.parseXML ''
|
||||||
|
<a>
|
||||||
|
<b>1</b>
|
||||||
|
<c><d>1</d></c>
|
||||||
|
</a>
|
||||||
|
'';
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,9 +2,11 @@
|
||||||
let
|
let
|
||||||
pkgs' = pkgs;
|
pkgs' = pkgs;
|
||||||
# TODO: Test login
|
# TODO: Test login
|
||||||
commonTestScript = appname: { nodes, ... }:
|
commonTestScript = appname: cfgPathFn: { nodes, ... }:
|
||||||
let
|
let
|
||||||
shbapp = nodes.server.shb.arr.${appname};
|
shbapp = nodes.server.shb.arr.${appname};
|
||||||
|
cfgPath = cfgPathFn shbapp;
|
||||||
|
apiKey = if (shbapp.settings ? ApiKey) then "01234567890123456789" else null;
|
||||||
hasSSL = !(isNull shbapp.ssl);
|
hasSSL = !(isNull shbapp.ssl);
|
||||||
fqdn = if hasSSL then "https://${appname}.example.com" else "http://${appname}.example.com";
|
fqdn = if hasSSL then "https://${appname}.example.com" else "http://${appname}.example.com";
|
||||||
healthUrl = "/health";
|
healthUrl = "/health";
|
||||||
|
@ -48,9 +50,15 @@ let
|
||||||
|
|
||||||
if response['code'] != 200:
|
if response['code'] != 200:
|
||||||
raise Exception(f"Code is {response['code']}")
|
raise Exception(f"Code is {response['code']}")
|
||||||
|
'' + lib.optionalString (apiKey != null) ''
|
||||||
|
|
||||||
|
with subtest("apikey"):
|
||||||
|
config = server.succeed("cat ${cfgPath}")
|
||||||
|
if "${apiKey}" not in config:
|
||||||
|
raise Exception(f"Unexpected API Key. Want '${apiKey}', got '{config}'")
|
||||||
'';
|
'';
|
||||||
|
|
||||||
basic = appname: pkgs.testers.runNixOSTest {
|
basic = appname: cfgPathFn: pkgs.testers.runNixOSTest {
|
||||||
name = "arr-${appname}-basic";
|
name = "arr-${appname}-basic";
|
||||||
|
|
||||||
nodes.server = { config, pkgs, ... }: {
|
nodes.server = { config, pkgs, ... }: {
|
||||||
|
@ -73,7 +81,7 @@ let
|
||||||
domain = "example.com";
|
domain = "example.com";
|
||||||
subdomain = appname;
|
subdomain = appname;
|
||||||
|
|
||||||
settings.APIKey.source = pkgs.writeText "APIKey" "01234567890123456789"; # Needs to be >=20 characters.
|
settings.ApiKey.source = pkgs.writeText "APIKey" "01234567890123456789"; # Needs to be >=20 characters.
|
||||||
};
|
};
|
||||||
# Nginx port.
|
# Nginx port.
|
||||||
networking.firewall.allowedTCPPorts = [ 80 ];
|
networking.firewall.allowedTCPPorts = [ 80 ];
|
||||||
|
@ -81,14 +89,14 @@ let
|
||||||
|
|
||||||
nodes.client = {};
|
nodes.client = {};
|
||||||
|
|
||||||
testScript = commonTestScript appname;
|
testScript = commonTestScript appname cfgPathFn;
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
radarr_basic = basic "radarr";
|
radarr_basic = basic "radarr" (cfg: "${cfg.dataDir}/config.xml");
|
||||||
sonarr_basic = basic "sonarr";
|
sonarr_basic = basic "sonarr" (cfg: "${cfg.dataDir}/config.xml");
|
||||||
bazarr_basic = basic "bazarr";
|
bazarr_basic = basic "bazarr" (cfg: "/var/lib/bazarr/config.xml");
|
||||||
readarr_basic = basic "readarr";
|
readarr_basic = basic "readarr" (cfg: "${cfg.dataDir}/config.xml");
|
||||||
lidarr_basic = basic "lidarr";
|
lidarr_basic = basic "lidarr" (cfg: "${cfg.dataDir}/config.xml");
|
||||||
jackett_basic = basic "jackett";
|
jackett_basic = basic "jackett" (cfg: "${cfg.dataDir}/ServerConfig.json");
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,10 +36,22 @@ in
|
||||||
inherit replacements;
|
inherit replacements;
|
||||||
};
|
};
|
||||||
|
|
||||||
replaceInTemplate2 = shblib.replaceSecrets {
|
replaceInTemplateJSON = shblib.replaceSecrets {
|
||||||
inherit userConfig;
|
inherit userConfig;
|
||||||
resultPath = "/var/lib/config2.yaml";
|
resultPath = "/var/lib/config.json";
|
||||||
generator = name: value: lib.generators.toJSON {} value;
|
generator = shblib.replaceSecretsFormatAdapter (pkgs.formats.json {});
|
||||||
|
};
|
||||||
|
|
||||||
|
replaceInTemplateJSONGen = shblib.replaceSecrets {
|
||||||
|
inherit userConfig;
|
||||||
|
resultPath = "/var/lib/config_gen.json";
|
||||||
|
generator = shblib.replaceSecretsGeneratorAdapter (lib.generators.toJSON {});
|
||||||
|
};
|
||||||
|
|
||||||
|
replaceInTemplateXML = shblib.replaceSecrets {
|
||||||
|
inherit userConfig;
|
||||||
|
resultPath = "/var/lib/config.xml";
|
||||||
|
generator = shblib.replaceSecretsFormatAdapter (shblib.formatXML {enclosingRoot = "Root";});
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
pkgs.testers.runNixOSTest {
|
pkgs.testers.runNixOSTest {
|
||||||
|
@ -60,27 +72,72 @@ in
|
||||||
|
|
||||||
system.activationScripts = {
|
system.activationScripts = {
|
||||||
libtest = replaceInTemplate;
|
libtest = replaceInTemplate;
|
||||||
libtest2 = replaceInTemplate2;
|
libtestJSON = replaceInTemplateJSON;
|
||||||
|
libtestJSONGen = replaceInTemplateJSONGen;
|
||||||
|
libtestXML = replaceInTemplateXML;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
testScript = { nodes, ... }: ''
|
testScript = { nodes, ... }: ''
|
||||||
import json
|
import json
|
||||||
|
from collections import ChainMap
|
||||||
|
from xml.etree import ElementTree
|
||||||
|
|
||||||
start_all()
|
start_all()
|
||||||
|
machine.wait_for_file("/var/lib/config.yaml")
|
||||||
|
machine.wait_for_file("/var/lib/config.json")
|
||||||
|
machine.wait_for_file("/var/lib/config_gen.json")
|
||||||
|
machine.wait_for_file("/var/lib/config.xml")
|
||||||
|
|
||||||
|
def xml_to_dict_recursive(root):
|
||||||
|
all_descendants = list(root)
|
||||||
|
if len(all_descendants) == 0:
|
||||||
|
return {root.tag: root.text}
|
||||||
|
else:
|
||||||
|
merged_dict = ChainMap(*map(xml_to_dict_recursive, all_descendants))
|
||||||
|
return {root.tag: dict(merged_dict)}
|
||||||
|
|
||||||
wantedConfig = json.loads('${lib.generators.toJSON {} wantedConfig}')
|
wantedConfig = json.loads('${lib.generators.toJSON {} wantedConfig}')
|
||||||
gotConfig = json.loads(machine.succeed("cat /var/lib/config.yaml"))
|
|
||||||
gotConfig2 = json.loads(machine.succeed("cat /var/lib/config2.yaml"))
|
|
||||||
|
|
||||||
# For debugging purpose
|
with subtest("config"):
|
||||||
print(machine.succeed("cat ${pkgs.writeText "replaceInTemplate" replaceInTemplate}"))
|
print(machine.succeed("cat ${pkgs.writeText "replaceInTemplate" replaceInTemplate}"))
|
||||||
print(machine.succeed("cat ${pkgs.writeText "replaceInTemplate2" replaceInTemplate2}"))
|
|
||||||
|
|
||||||
if wantedConfig != gotConfig:
|
gotConfig = machine.succeed("cat /var/lib/config.yaml")
|
||||||
raise Exception("\nwantedConfig: {}\n!= gotConfig: {}".format(wantedConfig, gotConfig))
|
print(gotConfig)
|
||||||
|
gotConfig = json.loads(gotConfig)
|
||||||
|
|
||||||
if wantedConfig != gotConfig2:
|
if wantedConfig != gotConfig:
|
||||||
raise Exception("\nwantedConfig: {}\n!= gotConfig2: {}".format(wantedConfig, gotConfig))
|
raise Exception("\nwantedConfig: {}\n!= gotConfig: {}".format(wantedConfig, gotConfig))
|
||||||
|
|
||||||
|
with subtest("config JSON Gen"):
|
||||||
|
print(machine.succeed("cat ${pkgs.writeText "replaceInTemplateJSONGen" replaceInTemplateJSONGen}"))
|
||||||
|
|
||||||
|
gotConfig = machine.succeed("cat /var/lib/config_gen.json")
|
||||||
|
print(gotConfig)
|
||||||
|
gotConfig = json.loads(gotConfig)
|
||||||
|
|
||||||
|
if wantedConfig != gotConfig:
|
||||||
|
raise Exception("\nwantedConfig: {}\n!= gotConfig: {}".format(wantedConfig, gotConfig))
|
||||||
|
|
||||||
|
with subtest("config JSON"):
|
||||||
|
print(machine.succeed("cat ${pkgs.writeText "replaceInTemplateJSON" replaceInTemplateJSON}"))
|
||||||
|
|
||||||
|
gotConfig = machine.succeed("cat /var/lib/config.json")
|
||||||
|
print(gotConfig)
|
||||||
|
gotConfig = json.loads(gotConfig)
|
||||||
|
|
||||||
|
if wantedConfig != gotConfig:
|
||||||
|
raise Exception("\nwantedConfig: {}\n!= gotConfig: {}".format(wantedConfig, gotConfig))
|
||||||
|
|
||||||
|
with subtest("config XML"):
|
||||||
|
print(machine.succeed("cat ${pkgs.writeText "replaceInTemplateXML" replaceInTemplateXML}"))
|
||||||
|
|
||||||
|
gotConfig = machine.succeed("cat /var/lib/config.xml")
|
||||||
|
print(gotConfig)
|
||||||
|
gotConfig = xml_to_dict_recursive(ElementTree.XML(gotConfig))['Root']
|
||||||
|
|
||||||
|
if wantedConfig != gotConfig:
|
||||||
|
raise Exception("\nwantedConfig: {}\n!= gotConfig: {}".format(wantedConfig, gotConfig))
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue