From 81bf639da70763a844b79ffecd5de59b83f5ecb2 Mon Sep 17 00:00:00 2001 From: Nicholas Hassan Date: Thu, 13 Feb 2025 14:17:16 +1030 Subject: [PATCH] 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 --- modules/services/podman-linux/builds.nix | 35 ++++---- modules/services/podman-linux/containers.nix | 90 +++++++++---------- modules/services/podman-linux/images.nix | 6 +- .../services/podman-linux/install-quadlet.nix | 14 +-- modules/services/podman-linux/networks.nix | 6 +- modules/services/podman-linux/volumes.nix | 6 +- .../integration-build-expected.service | 2 +- ...integration-container-bld-expected.service | 8 +- .../integration-container-expected.service | 18 ++-- .../services/podman-linux/integration.nix | 2 +- 10 files changed, 96 insertions(+), 91 deletions(-) diff --git a/modules/services/podman-linux/builds.nix b/modules/services/podman-linux/builds.nix index 937a206b0..c1aed0c47 100644 --- a/modules/services/podman-linux/builds.nix +++ b/modules/services/podman-linux/builds.nix @@ -7,7 +7,7 @@ let createQuadletSource = name: buildDef: let - buildConfig = podman-lib.deepMerge { + quadlet = podman-lib.deepMerge { Build = { AuthFile = buildDef.authFile; Environment = buildDef.environment; @@ -30,21 +30,26 @@ let }; Unit = { Description = buildDef.description; }; } buildDef.extraConfig; - in '' - # Automatically generated by home-manager for podman build configuration - # DO NOT EDIT THIS FILE DIRECTLY - # - # ${name}.build - ${podman-lib.toQuadletIni buildConfig} - ''; + in { + attrs = quadlet; + text = '' + # Automatically generated by home-manager for podman build configuration + # DO NOT EDIT THIS FILE DIRECTLY + # + # ${name}.build + ${podman-lib.toQuadletIni quadlet} + ''; + }; - toQuadletInternal = name: buildDef: { - assertions = podman-lib.buildConfigAsserts name buildDef.extraConfig; - serviceName = - "podman-${name}"; # quadlet service name: 'podman--build.service - source = podman-lib.removeBlankLines (createQuadletSource name buildDef); - resourceType = "build"; - }; + toQuadletInternal = name: buildDef: + let src = createQuadletSource name buildDef; + in { + assertions = podman-lib.buildConfigAsserts name buildDef.extraConfig; + serviceName = + "podman-${name}"; # generated service name: 'podman--build.service + source = podman-lib.removeBlankLines src.text; + resourceType = "build"; + }; in let buildDefinitionType = types.submodule ({ name, ... }: { options = { diff --git a/modules/services/podman-linux/containers.nix b/modules/services/podman-linux/containers.nix index 32e643a5b..93c02990d 100644 --- a/modules/services/podman-linux/containers.nix +++ b/modules/services/podman-linux/containers.nix @@ -9,20 +9,10 @@ let createQuadletSource = name: containerDef: let - # formatServiceNameForType = type: name: - # { - # image = "${name}-image.service"; - # build = "${name}-build.service"; - # network = "${name}-network.service"; - # volume = "${name}-volume.service"; - # }."${type}"; - dependencyBySuffix = type: name: if (hasInfix ".${type}" name) then - let - baseName = elemAt (splitString ".${type}" name) 0; - in - if (hasAttr (builtins.trace (baseName) baseName) cfg.internal.builtQuadlets) then + let baseName = elemAt (splitString ".${type}" name) 0; + in if (hasAttr baseName cfg.internal.builtQuadlets) then [ (cfg.internal.builtQuadlets.${baseName}) ] else [ ] @@ -30,25 +20,34 @@ let [ ]; withResolverFor = type: value: - let - resolve = v: dependencyBySuffix type v; - in - 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}" + let resolve = v: dependencyBySuffix type v; + in if builtins.isList value then + builtins.concatLists (map resolve value) # Flatten list of lists 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 { Container = { @@ -62,18 +61,18 @@ let EnvironmentFile = containerDef.environmentFile; Exec = containerDef.exec; Group = containerDef.group; - Image = resolvedImage; + Image = checkQuadletReference [ "build" "image" ] containerDef.image; IP = containerDef.ip4; IP6 = containerDef.ip6; Label = (containerDef.labels // { "nix.home-manager.managed" = true; }); - Network = containerDef.network; + Network = checkQuadletReference [ "network" ] containerDef.network; NetworkAlias = containerDef.networkAlias; PodmanArgs = containerDef.extraPodmanArgs; PublishPort = containerDef.ports; UserNS = containerDef.userNS; User = containerDef.user; - Volume = containerDef.volumes; + Volume = checkQuadletReference [ "volume" ] containerDef.volumes; }; Install = { WantedBy = optionals containerDef.autoStart [ @@ -99,9 +98,9 @@ let "Service for container ${name}"); }; } containerDef.extraConfig); - in - { + in { dependencies = dependencyServices; + attrs = quadlet; text = '' # Automatically generated by home-manager podman container configuration # DO NOT EDIT THIS FILE DIRECTLY @@ -111,17 +110,16 @@ let ''; }; - toQuadletInternal = name: containerDef: let - quadletSrc = createQuadletSource name containerDef; - in { - assertions = podman-lib.buildConfigAsserts name containerDef.extraConfig; - dependencies = quadletSrc.dependencies; - resourceType = "container"; - serviceName = - "podman-${name}"; # quadlet service name: 'podman-.service' - source = - podman-lib.removeBlankLines quadletSrc.text; - }; + toQuadletInternal = name: containerDef: + let src = createQuadletSource name containerDef; + in { + assertions = podman-lib.buildConfigAsserts name containerDef.extraConfig; + dependencies = src.dependencies; + resourceType = "container"; + serviceName = + "podman-${src.attrs.Container.ContainerName}"; # generated service name: 'podman-.service' + source = podman-lib.removeBlankLines src.text; + }; # Define the container user type as the user interface containerDefinitionType = types.submodule { diff --git a/modules/services/podman-linux/images.nix b/modules/services/podman-linux/images.nix index 51aa9f1be..957ed11bf 100644 --- a/modules/services/podman-linux/images.nix +++ b/modules/services/podman-linux/images.nix @@ -11,7 +11,7 @@ let (if imageDef.username != null then imageDef.username else "") + (if imageDef.password != null then ":${imageDef.password}" else ""); - imageConfig = podman-lib.deepMerge { + quadlet = podman-lib.deepMerge { Image = { AuthFile = imageDef.authFile; CertDir = imageDef.certDir; @@ -40,13 +40,13 @@ let # DO NOT EDIT THIS FILE DIRECTLY # # ${name}.image - ${podman-lib.toQuadletIni imageConfig} + ${podman-lib.toQuadletIni quadlet} ''; toQuadletInternal = name: imageDef: { assertions = podman-lib.buildConfigAsserts name imageDef.extraConfig; serviceName = - "podman-${name}"; # quadlet service name: 'podman--image.service + "podman-${name}"; # generated service name: 'podman--image.service source = podman-lib.removeBlankLines (createQuadletSource name imageDef); resourceType = "image"; }; diff --git a/modules/services/podman-linux/install-quadlet.nix b/modules/services/podman-linux/install-quadlet.nix index 2f909ae45..7a329b94e 100644 --- a/modules/services/podman-linux/install-quadlet.nix +++ b/modules/services/podman-linux/install-quadlet.nix @@ -15,13 +15,13 @@ let pkgs.stdenv.mkDerivation { name = "home-${quadlet.resourceType}-${quadlet.serviceName}"; - buildInputs = [ cfg.package ]; - - # dontUnpack = true; + buildInputs = [ cfg.package ] ++ quadlet.dependencies; unpackPhase = '' 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 = '' @@ -42,7 +42,6 @@ let }; }; - # Create a derivation for each quadlet spec builtQuadlets = map buildPodmanQuadlet cfg.internal.quadletDefinitions; accumulateUnitFiles = prefix: path: quadlet: @@ -87,6 +86,9 @@ in { lib.mkIf (lib.length builtQuadlets >= 1) (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); }; } diff --git a/modules/services/podman-linux/networks.nix b/modules/services/podman-linux/networks.nix index 8fee143e4..2ab9f2031 100644 --- a/modules/services/podman-linux/networks.nix +++ b/modules/services/podman-linux/networks.nix @@ -9,7 +9,7 @@ let createQuadletSource = name: networkDef: let - cfg = (podman-lib.deepMerge { + quadlet = (podman-lib.deepMerge { Install = { WantedBy = (if networkDef.autoStart then [ "default.target" @@ -50,13 +50,13 @@ let # DO NOT EDIT THIS FILE DIRECTLY # # ${name}.network - ${podman-lib.toQuadletIni cfg} + ${podman-lib.toQuadletIni quadlet} ''; toQuadletInternal = name: networkDef: { assertions = podman-lib.buildConfigAsserts name networkDef.extraConfig; serviceName = - "podman-${name}"; # quadlet service name: 'podman--network.service' + "podman-${name}"; # generated service name: 'podman--network.service' source = podman-lib.removeBlankLines (createQuadletSource name networkDef); resourceType = "network"; }; diff --git a/modules/services/podman-linux/volumes.nix b/modules/services/podman-linux/volumes.nix index 10f7cd927..93c8dcd4a 100644 --- a/modules/services/podman-linux/volumes.nix +++ b/modules/services/podman-linux/volumes.nix @@ -9,7 +9,7 @@ let createQuadletSource = name: volumeDef: let - volumeConfig = podman-lib.deepMerge { + quadlet = podman-lib.deepMerge { Install = { WantedBy = optionals volumeDef.autoStart [ "default.target" @@ -49,13 +49,13 @@ let # DO NOT EDIT THIS FILE DIRECTLY # # ${name}.volume - ${podman-lib.toQuadletIni volumeConfig} + ${podman-lib.toQuadletIni quadlet} ''; toQuadletInternal = name: volumeDef: { assertions = podman-lib.buildConfigAsserts name volumeDef.extraConfig; serviceName = - "podman-${name}"; # quadlet service name: 'podman--volume.service' + "podman-${name}"; # generated service name: 'podman--volume.service' source = podman-lib.removeBlankLines (createQuadletSource name volumeDef); resourceType = "volume"; }; diff --git a/tests/modules/services/podman-linux/integration-build-expected.service b/tests/modules/services/podman-linux/integration-build-expected.service index ab4064c22..03808a4cf 100644 --- a/tests/modules/services/podman-linux/integration-build-expected.service +++ b/tests/modules/services/podman-linux/integration-build-expected.service @@ -27,4 +27,4 @@ Wants=podman-user-wait-network-online.service After=podman-user-wait-network-online.service Description=Service for build my-bld 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 diff --git a/tests/modules/services/podman-linux/integration-container-bld-expected.service b/tests/modules/services/podman-linux/integration-container-bld-expected.service index 7201fea35..520e53ee0 100644 --- a/tests/modules/services/podman-linux/integration-container-bld-expected.service +++ b/tests/modules/services/podman-linux/integration-container-bld-expected.service @@ -7,7 +7,7 @@ [X-Container] ContainerName=my-container-bld Environment= -Image=localhost/homemanager/my-bld +Image=podman-my-bld.build Label=nix.home-manager.managed=true [Install] @@ -26,13 +26,13 @@ Delegate=yes Type=notify NotifyAccess=all 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] Wants=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 -Requires=podman-my-bld-build.service 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 diff --git a/tests/modules/services/podman-linux/integration-container-expected.service b/tests/modules/services/podman-linux/integration-container-expected.service index ca92559d4..f9a01d7a0 100644 --- a/tests/modules/services/podman-linux/integration-container-expected.service +++ b/tests/modules/services/podman-linux/integration-container-expected.service @@ -7,11 +7,11 @@ [X-Container] ContainerName=my-container Environment= -Image=docker.io/alpine:latest +Image=podman-my-img.image Label=nix.home-manager.managed=true -Network=my-net +Network=podman-my-net.network Network=externalnet -Volume=my-vol:/data +Volume=podman-my-vol.volume:/data [Install] WantedBy=default.target @@ -34,12 +34,12 @@ ExecStart=/nix/store/00000000000000000000000000000000-podman/bin/podman run --na [Unit] Wants=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 -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 +Requires=podman-my-img-image.service +After=podman-my-img-image.service 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 diff --git a/tests/modules/services/podman-linux/integration.nix b/tests/modules/services/podman-linux/integration.nix index 565c22cca..8e2e272fd 100644 --- a/tests/modules/services/podman-linux/integration.nix +++ b/tests/modules/services/podman-linux/integration.nix @@ -21,7 +21,7 @@ network = [ "my-net.network" "externalnet" ]; 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"; }; networks."my-net" = {