1
0
Fork 0
mirror of https://github.com/nix-community/home-manager.git synced 2025-12-01 14:41:03 +01:00

podman: link dependent quadlets during build

podman's systemd generator can automatically resolve unit dependencies, so instead of us guessing these links to create them, we provide the sources during generation
This commit is contained in:
Nicholas Hassan 2025-02-13 14:17:16 +10:30 committed by Austin Horstman
parent 4108ec3aa8
commit 81bf639da7
10 changed files with 96 additions and 91 deletions

View file

@ -7,7 +7,7 @@ let
createQuadletSource = name: buildDef: createQuadletSource = name: buildDef:
let let
buildConfig = podman-lib.deepMerge { quadlet = podman-lib.deepMerge {
Build = { Build = {
AuthFile = buildDef.authFile; AuthFile = buildDef.authFile;
Environment = buildDef.environment; Environment = buildDef.environment;
@ -30,21 +30,26 @@ let
}; };
Unit = { Description = buildDef.description; }; Unit = { Description = buildDef.description; };
} buildDef.extraConfig; } buildDef.extraConfig;
in '' in {
# Automatically generated by home-manager for podman build configuration attrs = quadlet;
# DO NOT EDIT THIS FILE DIRECTLY text = ''
# # Automatically generated by home-manager for podman build configuration
# ${name}.build # DO NOT EDIT THIS FILE DIRECTLY
${podman-lib.toQuadletIni buildConfig} #
''; # ${name}.build
${podman-lib.toQuadletIni quadlet}
'';
};
toQuadletInternal = name: buildDef: { toQuadletInternal = name: buildDef:
assertions = podman-lib.buildConfigAsserts name buildDef.extraConfig; let src = createQuadletSource name buildDef;
serviceName = in {
"podman-${name}"; # quadlet service name: 'podman-<name>-build.service assertions = podman-lib.buildConfigAsserts name buildDef.extraConfig;
source = podman-lib.removeBlankLines (createQuadletSource name buildDef); serviceName =
resourceType = "build"; "podman-${name}"; # generated service name: 'podman-<name>-build.service
}; source = podman-lib.removeBlankLines src.text;
resourceType = "build";
};
in let in let
buildDefinitionType = types.submodule ({ name, ... }: { buildDefinitionType = types.submodule ({ name, ... }: {
options = { options = {

View file

@ -9,20 +9,10 @@ let
createQuadletSource = name: containerDef: createQuadletSource = name: containerDef:
let let
# formatServiceNameForType = type: name:
# {
# image = "${name}-image.service";
# build = "${name}-build.service";
# network = "${name}-network.service";
# volume = "${name}-volume.service";
# }."${type}";
dependencyBySuffix = type: name: dependencyBySuffix = type: name:
if (hasInfix ".${type}" name) then if (hasInfix ".${type}" name) then
let let baseName = elemAt (splitString ".${type}" name) 0;
baseName = elemAt (splitString ".${type}" name) 0; in if (hasAttr baseName cfg.internal.builtQuadlets) then
in
if (hasAttr (builtins.trace (baseName) baseName) cfg.internal.builtQuadlets) then
[ (cfg.internal.builtQuadlets.${baseName}) ] [ (cfg.internal.builtQuadlets.${baseName}) ]
else else
[ ] [ ]
@ -30,25 +20,34 @@ let
[ ]; [ ];
withResolverFor = type: value: withResolverFor = type: value:
let let resolve = v: dependencyBySuffix type v;
resolve = v: dependencyBySuffix type v; in if builtins.isList value then
in builtins.concatLists (map resolve value) # Flatten list of lists
if builtins.isList value
then builtins.concatLists (map resolve value) # Flatten list of lists
else resolve value;
dependencyServices = (withResolverFor "image" containerDef.image) ++
(withResolverFor "build" containerDef.image) ++
(withResolverFor "network" containerDef.network) ++
(withResolverFor "volume" containerDef.volumes);
getActualImage =
if (builtins.hasAttr containerDef.image cfg.images) then
cfg.images."${containerDef.image}".image
else if (builtins.hasAttr containerDef.image cfg.builds) then
"localhost/homemanager/${containerDef.image}"
else else
containerDef.image; resolve value;
dependencyServices = (withResolverFor "image" containerDef.image)
++ (withResolverFor "build" containerDef.image)
++ (withResolverFor "network" containerDef.network)
++ (withResolverFor "volume" containerDef.volumes);
checkQuadletReference = types: value:
if builtins.isList value then
builtins.concatLists (map (checkQuadletReference types) value)
else
let type = findFirst (t: hasInfix ".${t}" value) null types;
in if (type != null) then
let
baseName = elemAt (splitString ".${type}" value) 0;
quadletsOfType =
filterAttrs (n: v: v.quadletData.resourceType == type)
cfg.internal.builtQuadlets;
in if (hasAttr baseName quadletsOfType) then
[ (replaceStrings [ baseName ] [ "podman-${baseName}" ] value) ]
else
[ value ]
else
[ value ];
quadlet = (podman-lib.deepMerge { quadlet = (podman-lib.deepMerge {
Container = { Container = {
@ -62,18 +61,18 @@ let
EnvironmentFile = containerDef.environmentFile; EnvironmentFile = containerDef.environmentFile;
Exec = containerDef.exec; Exec = containerDef.exec;
Group = containerDef.group; Group = containerDef.group;
Image = resolvedImage; Image = checkQuadletReference [ "build" "image" ] containerDef.image;
IP = containerDef.ip4; IP = containerDef.ip4;
IP6 = containerDef.ip6; IP6 = containerDef.ip6;
Label = Label =
(containerDef.labels // { "nix.home-manager.managed" = true; }); (containerDef.labels // { "nix.home-manager.managed" = true; });
Network = containerDef.network; Network = checkQuadletReference [ "network" ] containerDef.network;
NetworkAlias = containerDef.networkAlias; NetworkAlias = containerDef.networkAlias;
PodmanArgs = containerDef.extraPodmanArgs; PodmanArgs = containerDef.extraPodmanArgs;
PublishPort = containerDef.ports; PublishPort = containerDef.ports;
UserNS = containerDef.userNS; UserNS = containerDef.userNS;
User = containerDef.user; User = containerDef.user;
Volume = containerDef.volumes; Volume = checkQuadletReference [ "volume" ] containerDef.volumes;
}; };
Install = { Install = {
WantedBy = optionals containerDef.autoStart [ WantedBy = optionals containerDef.autoStart [
@ -99,9 +98,9 @@ let
"Service for container ${name}"); "Service for container ${name}");
}; };
} containerDef.extraConfig); } containerDef.extraConfig);
in in {
{
dependencies = dependencyServices; dependencies = dependencyServices;
attrs = quadlet;
text = '' text = ''
# Automatically generated by home-manager podman container configuration # Automatically generated by home-manager podman container configuration
# DO NOT EDIT THIS FILE DIRECTLY # DO NOT EDIT THIS FILE DIRECTLY
@ -111,17 +110,16 @@ let
''; '';
}; };
toQuadletInternal = name: containerDef: let toQuadletInternal = name: containerDef:
quadletSrc = createQuadletSource name containerDef; let src = createQuadletSource name containerDef;
in { in {
assertions = podman-lib.buildConfigAsserts name containerDef.extraConfig; assertions = podman-lib.buildConfigAsserts name containerDef.extraConfig;
dependencies = quadletSrc.dependencies; dependencies = src.dependencies;
resourceType = "container"; resourceType = "container";
serviceName = serviceName =
"podman-${name}"; # quadlet service name: 'podman-<name>.service' "podman-${src.attrs.Container.ContainerName}"; # generated service name: 'podman-<name>.service'
source = source = podman-lib.removeBlankLines src.text;
podman-lib.removeBlankLines quadletSrc.text; };
};
# Define the container user type as the user interface # Define the container user type as the user interface
containerDefinitionType = types.submodule { containerDefinitionType = types.submodule {

View file

@ -11,7 +11,7 @@ let
(if imageDef.username != null then imageDef.username else "") (if imageDef.username != null then imageDef.username else "")
+ (if imageDef.password != null then ":${imageDef.password}" else ""); + (if imageDef.password != null then ":${imageDef.password}" else "");
imageConfig = podman-lib.deepMerge { quadlet = podman-lib.deepMerge {
Image = { Image = {
AuthFile = imageDef.authFile; AuthFile = imageDef.authFile;
CertDir = imageDef.certDir; CertDir = imageDef.certDir;
@ -40,13 +40,13 @@ let
# DO NOT EDIT THIS FILE DIRECTLY # DO NOT EDIT THIS FILE DIRECTLY
# #
# ${name}.image # ${name}.image
${podman-lib.toQuadletIni imageConfig} ${podman-lib.toQuadletIni quadlet}
''; '';
toQuadletInternal = name: imageDef: { toQuadletInternal = name: imageDef: {
assertions = podman-lib.buildConfigAsserts name imageDef.extraConfig; assertions = podman-lib.buildConfigAsserts name imageDef.extraConfig;
serviceName = serviceName =
"podman-${name}"; # quadlet service name: 'podman-<name>-image.service "podman-${name}"; # generated service name: 'podman-<name>-image.service
source = podman-lib.removeBlankLines (createQuadletSource name imageDef); source = podman-lib.removeBlankLines (createQuadletSource name imageDef);
resourceType = "image"; resourceType = "image";
}; };

View file

@ -15,13 +15,13 @@ let
pkgs.stdenv.mkDerivation { pkgs.stdenv.mkDerivation {
name = "home-${quadlet.resourceType}-${quadlet.serviceName}"; name = "home-${quadlet.resourceType}-${quadlet.serviceName}";
buildInputs = [ cfg.package ]; buildInputs = [ cfg.package ] ++ quadlet.dependencies;
# dontUnpack = true;
unpackPhase = '' unpackPhase = ''
mkdir -p $out/quadlets mkdir -p $out/quadlets
${concatStringsSep "\n" (map (v: "cp ${v.out}/quadlets/${v.quadletData.serviceName}.${v.quadletData.resourceType} $out/quadlets") quadlet.dependencies)} ${concatStringsSep "\n" (map (v:
"ln -s ${v.out}/quadlets/${v.quadletData.serviceName}.${v.quadletData.resourceType} $out/quadlets")
quadlet.dependencies)}
''; '';
installPhase = '' installPhase = ''
@ -42,7 +42,6 @@ let
}; };
}; };
# Create a derivation for each quadlet spec
builtQuadlets = map buildPodmanQuadlet cfg.internal.quadletDefinitions; builtQuadlets = map buildPodmanQuadlet cfg.internal.quadletDefinitions;
accumulateUnitFiles = prefix: path: quadlet: accumulateUnitFiles = prefix: path: quadlet:
@ -87,6 +86,9 @@ in {
lib.mkIf (lib.length builtQuadlets >= 1) lib.mkIf (lib.length builtQuadlets >= 1)
(lib.hm.dag.entryAfter [ "reloadSystemd" ] activationCleanupScript); (lib.hm.dag.entryAfter [ "reloadSystemd" ] activationCleanupScript);
services.podman.internal.builtQuadlets = listToAttrs (map (pkg: { name = removePrefix "podman-" pkg.passthru.quadletData.serviceName; value = pkg; }) builtQuadlets); services.podman.internal.builtQuadlets = listToAttrs (map (pkg: {
name = removePrefix "podman-" pkg.passthru.quadletData.serviceName;
value = pkg;
}) builtQuadlets);
}; };
} }

View file

@ -9,7 +9,7 @@ let
createQuadletSource = name: networkDef: createQuadletSource = name: networkDef:
let let
cfg = (podman-lib.deepMerge { quadlet = (podman-lib.deepMerge {
Install = { Install = {
WantedBy = (if networkDef.autoStart then [ WantedBy = (if networkDef.autoStart then [
"default.target" "default.target"
@ -50,13 +50,13 @@ let
# DO NOT EDIT THIS FILE DIRECTLY # DO NOT EDIT THIS FILE DIRECTLY
# #
# ${name}.network # ${name}.network
${podman-lib.toQuadletIni cfg} ${podman-lib.toQuadletIni quadlet}
''; '';
toQuadletInternal = name: networkDef: { toQuadletInternal = name: networkDef: {
assertions = podman-lib.buildConfigAsserts name networkDef.extraConfig; assertions = podman-lib.buildConfigAsserts name networkDef.extraConfig;
serviceName = serviceName =
"podman-${name}"; # quadlet service name: 'podman-<name>-network.service' "podman-${name}"; # generated service name: 'podman-<name>-network.service'
source = podman-lib.removeBlankLines (createQuadletSource name networkDef); source = podman-lib.removeBlankLines (createQuadletSource name networkDef);
resourceType = "network"; resourceType = "network";
}; };

View file

@ -9,7 +9,7 @@ let
createQuadletSource = name: volumeDef: createQuadletSource = name: volumeDef:
let let
volumeConfig = podman-lib.deepMerge { quadlet = podman-lib.deepMerge {
Install = { Install = {
WantedBy = optionals volumeDef.autoStart [ WantedBy = optionals volumeDef.autoStart [
"default.target" "default.target"
@ -49,13 +49,13 @@ let
# DO NOT EDIT THIS FILE DIRECTLY # DO NOT EDIT THIS FILE DIRECTLY
# #
# ${name}.volume # ${name}.volume
${podman-lib.toQuadletIni volumeConfig} ${podman-lib.toQuadletIni quadlet}
''; '';
toQuadletInternal = name: volumeDef: { toQuadletInternal = name: volumeDef: {
assertions = podman-lib.buildConfigAsserts name volumeDef.extraConfig; assertions = podman-lib.buildConfigAsserts name volumeDef.extraConfig;
serviceName = serviceName =
"podman-${name}"; # quadlet service name: 'podman-<name>-volume.service' "podman-${name}"; # generated service name: 'podman-<name>-volume.service'
source = podman-lib.removeBlankLines (createQuadletSource name volumeDef); source = podman-lib.removeBlankLines (createQuadletSource name volumeDef);
resourceType = "volume"; resourceType = "volume";
}; };

View file

@ -27,4 +27,4 @@ Wants=podman-user-wait-network-online.service
After=podman-user-wait-network-online.service After=podman-user-wait-network-online.service
Description=Service for build my-bld Description=Service for build my-bld
RequiresMountsFor=%t/containers RequiresMountsFor=%t/containers
SourcePath=/nix/store/00000000000000000000000000000000-home-build-podman-my-bld/quadlets/podman-my-bld.build SourcePath=/nix/store/00000000000000000000000000000000-home-container-podman-my-container-bld/quadlets/podman-my-bld.build

View file

@ -7,7 +7,7 @@
[X-Container] [X-Container]
ContainerName=my-container-bld ContainerName=my-container-bld
Environment= Environment=
Image=localhost/homemanager/my-bld Image=podman-my-bld.build
Label=nix.home-manager.managed=true Label=nix.home-manager.managed=true
[Install] [Install]
@ -26,13 +26,13 @@ Delegate=yes
Type=notify Type=notify
NotifyAccess=all NotifyAccess=all
SyslogIdentifier=%N SyslogIdentifier=%N
ExecStart=/nix/store/00000000000000000000000000000000-podman/bin/podman run --name my-container-bld --cidfile=%t/%N.cid --replace --rm --cgroups=split --sdnotify=conmon -d --label nix.home-manager.managed=true localhost/homemanager/my-bld ExecStart=/nix/store/00000000000000000000000000000000-podman/bin/podman run --name my-container-bld --cidfile=%t/%N.cid --replace --rm --cgroups=split --sdnotify=conmon -d --label nix.home-manager.managed=true homemanager/my-bld
[Unit] [Unit]
Wants=podman-user-wait-network-online.service Wants=podman-user-wait-network-online.service
After=podman-user-wait-network-online.service After=podman-user-wait-network-online.service
After=podman-my-bld-build.service
Description=Service for container my-container-bld Description=Service for container my-container-bld
Requires=podman-my-bld-build.service
SourcePath=/nix/store/00000000000000000000000000000000-home-container-podman-my-container-bld/quadlets/podman-my-container-bld.container SourcePath=/nix/store/00000000000000000000000000000000-home-container-podman-my-container-bld/quadlets/podman-my-container-bld.container
Requires=podman-my-bld-build.service
After=podman-my-bld-build.service
RequiresMountsFor=%t/containers RequiresMountsFor=%t/containers

View file

@ -7,11 +7,11 @@
[X-Container] [X-Container]
ContainerName=my-container ContainerName=my-container
Environment= Environment=
Image=docker.io/alpine:latest Image=podman-my-img.image
Label=nix.home-manager.managed=true Label=nix.home-manager.managed=true
Network=my-net Network=podman-my-net.network
Network=externalnet Network=externalnet
Volume=my-vol:/data Volume=podman-my-vol.volume:/data
[Install] [Install]
WantedBy=default.target WantedBy=default.target
@ -34,12 +34,12 @@ ExecStart=/nix/store/00000000000000000000000000000000-podman/bin/podman run --na
[Unit] [Unit]
Wants=podman-user-wait-network-online.service Wants=podman-user-wait-network-online.service
After=podman-user-wait-network-online.service After=podman-user-wait-network-online.service
After=podman-my-img-image.service
After=podman-my-net-network.service
After=podman-my-vol-volume.service
Description=Service for container my-container Description=Service for container my-container
Requires=podman-my-img-image.service
Requires=podman-my-net-network.service
Requires=podman-my-vol-volume.service
SourcePath=/nix/store/00000000000000000000000000000000-home-container-podman-my-container/quadlets/podman-my-container.container SourcePath=/nix/store/00000000000000000000000000000000-home-container-podman-my-container/quadlets/podman-my-container.container
Requires=podman-my-img-image.service
After=podman-my-img-image.service
RequiresMountsFor=%t/containers RequiresMountsFor=%t/containers
Requires=podman-my-net-network.service
After=podman-my-net-network.service
Requires=podman-my-vol-volume.service
After=podman-my-vol-volume.service

View file

@ -21,7 +21,7 @@
network = [ "my-net.network" "externalnet" ]; network = [ "my-net.network" "externalnet" ];
volumes = [ "my-vol.volume:/data" ]; volumes = [ "my-vol.volume:/data" ];
}; };
"my-container-bld" = { image = "my-bld"; }; "my-container-bld" = { image = "my-bld.build"; };
}; };
images."my-img" = { image = "docker.io/alpine:latest"; }; images."my-img" = { image = "docker.io/alpine:latest"; };
networks."my-net" = { networks."my-net" = {