1
0
Fork 0
mirror of https://github.com/nix-community/home-manager.git synced 2025-03-27 18:31:12 +00:00
home-manager/modules/services/podman-linux/podman-lib.nix
bamhm182 ce9cb2496c
podman: added volume, image, and build quadlets (#6137)
Added support for build, image, and volume quadlets
Resolved test failures due to podman 5.3.0 upgrade
Replaced several instances of pkgs.podman with services.podman.package
2025-03-09 23:02:05 -05:00

161 lines
6.2 KiB
Nix

{ pkgs, lib, config, ... }:
with lib;
let
normalizeKeyValue = k: v:
let
v' = (if builtins.isBool v then
(if v then "true" else "false")
else if builtins.isAttrs v then
(concatStringsSep ''
${k}='' (mapAttrsToList normalizeKeyValue v))
else
builtins.toString v);
in if builtins.isNull v then "" else "${k}=${v'}";
primitiveAttrs = with types; attrsOf (either primitive (listOf primitive));
primitiveList = with types; listOf primitive;
primitive = with types; nullOr (oneOf [ bool int str path ]);
toQuadletIni = generators.toINI {
listsAsDuplicateKeys = true;
mkKeyValue = normalizeKeyValue;
};
# meant for ini. favours b when two values are unmergeable
deepMerge = a: b:
foldl' (result: key:
let
aVal = if builtins.hasAttr key a then a.${key} else null;
bVal = if builtins.hasAttr key b then b.${key} else null;
# check if the types inside a list match the type of a primitive
listMatchesType = (list: val:
isList list && builtins.length list > 0
&& builtins.typeOf (builtins.head list) == builtins.typeOf val);
in if isAttrs aVal && isAttrs bVal then
result // { ${key} = deepMerge aVal bVal; }
else if isList aVal && isList bVal then
result // { ${key} = aVal ++ bVal; }
else if aVal == bVal then
result // { ${key} = aVal; }
else if aVal == null then
result // { ${key} = bVal; }
else if bVal == null then
result // { ${key} = aVal; }
else if isList aVal && listMatchesType aVal bVal then
result // { ${key} = aVal ++ [ bVal ]; }
else if isList bVal && listMatchesType bVal aVal then
result // { ${key} = [ aVal ] ++ bVal; }
else if builtins.typeOf aVal == builtins.typeOf bVal then
result // { ${key} = bVal; }
else
result // { ${key} = bVal; }) a (builtins.attrNames b);
in {
inherit primitiveAttrs;
inherit primitiveList;
inherit primitive;
inherit toQuadletIni;
inherit deepMerge;
buildConfigAsserts = quadletName: extraConfig:
let
configRules = {
Build = { ImageTag = (with types; listOf str); };
Container = { ContainerName = types.enum [ quadletName ]; };
Network = { NetworkName = types.enum [ quadletName ]; };
Volume = { VolumeName = types.enum [ quadletName ]; };
};
# Function to build assertions for a specific section and its attributes.
buildSectionAsserts = section: attrs:
if builtins.hasAttr section configRules then
flatten (mapAttrsToList (attrName: attrValue:
if builtins.hasAttr attrName configRules.${section} then [{
assertion = configRules.${section}.${attrName}.check attrValue;
message = "In '${quadletName}' config. ${section}.${attrName}: '${
toString attrValue
}' does not match expected type: ${
configRules.${section}.${attrName}.description
}";
}] else
[ ]) attrs)
else
[ ];
checkImageTag = extraConfig:
let
imageTags = (extraConfig.Build or { }).ImageTag or [ ];
containsRequiredTag =
builtins.elem "homemanager/${quadletName}" imageTags;
imageTagsStr = concatMapStringsSep ''" "'' toString imageTags;
in [{
assertion = imageTags == [ ] || containsRequiredTag;
message = ''
In '${quadletName}' config. Build.ImageTag: '[ "${imageTagsStr}" ]' does not contain 'homemanager/${quadletName}'.'';
}];
# Flatten assertions from all sections in `extraConfig`.
in flatten (concatLists [
(mapAttrsToList buildSectionAsserts extraConfig)
(checkImageTag extraConfig)
]);
extraConfigType = with types;
attrsOf (attrsOf (oneOf [ primitiveAttrs primitiveList primitive ]));
# input expects a list of quadletInternalType with all the same resourceType
generateManifestText = quadlets:
let
# create a list of all unique quadlet.resourceType in quadlets
quadletTypes = unique (map (quadlet: quadlet.resourceType) quadlets);
# if quadletTypes is > 1, then all quadlets are not the same type
allQuadletsSameType = length quadletTypes <= 1;
# ensures the service name is formatted correctly to be easily read
# by the activation script and matches `podman <resource> ls` output
formatServiceName = quadlet:
let
# remove the podman- prefix from the service name string
strippedName =
builtins.replaceStrings [ "podman-" ] [ "" ] quadlet.serviceName;
# specific logic for writing the unit name goes here. It should be
# identical to what `podman <resource> ls` shows
in {
"build" = "localhost/homemanager/${strippedName}";
"container" = strippedName;
"network" = strippedName;
"volume" = strippedName;
}."${quadlet.resourceType}";
in if allQuadletsSameType then ''
${concatStringsSep "\n"
(map (quadlet: formatServiceName quadlet) quadlets)}
'' else
abort ''
All quadlets must be of the same type.
Quadlet types in this manifest: ${concatStringsSep ", " quadletTypes}
'';
# podman requires setuid on newuidmad, so it cannot be provided by pkgs.shadow
# Including all possible locations in PATH for newuidmap is a workaround.
# NixOS provides a 'wrapped' variant at /run/wrappers/bin/newuidmap.
# Other distros must install the 'uidmap' package, ie for ubuntu: apt install uidmap.
# Extra paths are added to handle where distro package managers may put the uidmap binaries.
#
# Tracking for a potential solution: https://github.com/NixOS/nixpkgs/issues/138423
newuidmapPaths = "/run/wrappers/bin:/usr/bin:/bin:/usr/sbin:/sbin";
removeBlankLines = text:
let
lines = splitString "\n" text;
nonEmptyLines = filter (line: line != "") lines;
in concatStringsSep "\n" nonEmptyLines;
awaitPodmanUnshare = pkgs.writeShellScript "await-podman-unshare" ''
until ${config.services.podman.package}/bin/podman unshare ${pkgs.coreutils}/bin/true; do
${pkgs.coreutils}/bin/sleep 1
done
'';
}