Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 112 additions & 0 deletions nixos/lib/utils.nix
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,116 @@ rec {
throw "${shell} is not a shell package"
else
shell;

/* Recurse into a list or an attrset, searching for attrs named like
the value of the "attr" parameter, and return an attrset where the
names are the corresponding jq path where the attrs were found and
the values are the values of the attrs.

Example:
recursiveGetAttrWithJqPrefix {
example = [
{
irrelevant = "not interesting";
}
{
ignored = "ignored attr";
relevant = {
secret = {
_secret = "/path/to/secret";
};
};
}
];
} "_secret" -> { ".example[1].relevant.secret" = "/path/to/secret"; }
*/
recursiveGetAttrWithJqPrefix = item: attr:
let
recurse = prefix: item:
if item ? ${attr} then
nameValuePair prefix item.${attr}
else if isAttrs item then
map (name: recurse (prefix + "." + name) item.${name}) (attrNames item)
else if isList item then
imap0 (index: item: recurse (prefix + "[${toString index}]") item) item
else
[];
in listToAttrs (flatten (recurse "" item));

/* Takes an attrset and a file path and generates a bash snippet that
outputs a JSON file at the file path with all instances of

{ _secret = "/path/to/secret" }

in the attrset replaced with the contents of the file
"/path/to/secret" in the output JSON.

When a configuration option accepts an attrset that is finally
converted to JSON, this makes it possible to let the user define
arbitrary secret values.

Example:
If the file "/path/to/secret" contains the string
"topsecretpassword1234",

genJqSecretsReplacementSnippet {
example = [
{
irrelevant = "not interesting";
}
{
ignored = "ignored attr";
relevant = {
secret = {
_secret = "/path/to/secret";
};
};
}
];
} "/path/to/output.json"

would generate a snippet that, when run, outputs the following
JSON file at "/path/to/output.json":

{
"example": [
{
"irrelevant": "not interesting"
},
{
"ignored": "ignored attr",
"relevant": {
"secret": "topsecretpassword1234"
}
}
]
}
*/
genJqSecretsReplacementSnippet = genJqSecretsReplacementSnippet' "_secret";

# Like genJqSecretsReplacementSnippet, but allows the name of the
# attr which identifies the secret to be changed.
genJqSecretsReplacementSnippet' = attr: set: output:
let
secrets = recursiveGetAttrWithJqPrefix set attr;
in ''
if [[ -h '${output}' ]]; then
rm '${output}'
fi
''
+ concatStringsSep
"\n"
(imap1 (index: name: "export secret${toString index}=$(<'${secrets.${name}}')")
(attrNames secrets))
+ "\n"
+ "${pkgs.jq}/bin/jq >'${output}' '"
+ concatStringsSep
" | "
(imap1 (index: name: ''${name} = $ENV.secret${toString index}'')
(attrNames secrets))
+ ''
' <<'EOF'
${builtins.toJSON set}
EOF
'';
}
Loading