From 1fa03a1432f062b6494d7ec831533008ca54564c Mon Sep 17 00:00:00 2001 From: Salih Muhammed Date: Fri, 3 Jul 2026 17:17:26 +0300 Subject: [PATCH] quadlet: expand %N specifier in the Pod key The Pod key of a .container unit accepts systemd specifiers, but handlePod looked the referenced pod up in the units map using the raw value, so "Pod=%N.pod" failed with "quadlet pod unit %N.pod does not exist" instead of resolving to the pod named after the container's service. Expand %N to the container's service name before the lookup, matching the existing handling in GetContainerResourceName. Since %N is the service name, setting ServiceName= lets the container and pod unit files use different names. Add e2e cases for both combinations: a pod named after the container file, and a pod named after a ServiceName that differs from the file name. Fixes: #29108 Signed-off-by: Salih Muhammed --- docs/source/markdown/podman-container.unit.5.md.in | 6 ++++++ pkg/systemd/quadlet/quadlet.go | 4 ++++ test/e2e/quadlet/pod-specifier-svc.pod | 1 + test/e2e/quadlet/podspecifier.container | 7 +++++++ test/e2e/quadlet/podspecifier.pod | 1 + test/e2e/quadlet/podspecifier.servicename.container | 8 ++++++++ test/e2e/quadlet_test.go | 2 ++ 7 files changed, 29 insertions(+) create mode 100644 test/e2e/quadlet/pod-specifier-svc.pod create mode 100644 test/e2e/quadlet/podspecifier.container create mode 100644 test/e2e/quadlet/podspecifier.pod create mode 100644 test/e2e/quadlet/podspecifier.servicename.container diff --git a/docs/source/markdown/podman-container.unit.5.md.in b/docs/source/markdown/podman-container.unit.5.md.in index 82908718ab9..7bba9d6377d 100644 --- a/docs/source/markdown/podman-container.unit.5.md.in +++ b/docs/source/markdown/podman-container.unit.5.md.in @@ -273,6 +273,11 @@ setting up a container healthcheck, see the `HealthCmd` option for more. Specify a Quadlet `.pod` unit to link the container to. The value must take the form of `.pod` and the `.pod` unit must exist. +In this value, Quadlet expands the `%N` specifier to the container's service name (the +`ServiceName=` value if set, otherwise the name derived from the unit file), for example +`Pod=%N.pod`. The expansion happens while the unit is generated, and `%N` is the only +specifier resolved in this key. + Quadlet will add all the necessary parameters to link between the container and the pod and between their corresponding services. @@ -443,6 +448,7 @@ DefaultDependencies=false By default, the container is named `systemd-`. Use `ContainerName=` to override. Avoid using systemd specifiers like `%N` in resource names—they break inter-resource linking. +The exception is `%N` in `Pod=`, which Quadlet expands while generating the unit. # TEMPLATE UNITS diff --git a/pkg/systemd/quadlet/quadlet.go b/pkg/systemd/quadlet/quadlet.go index 123c964e5a3..fb9dcaac7ea 100644 --- a/pkg/systemd/quadlet/quadlet.go +++ b/pkg/systemd/quadlet/quadlet.go @@ -2214,6 +2214,10 @@ func createBasePodmanCommand(unitFile *parser.UnitFile, groupName string) *Podma func handlePod(quadletUnitFile, serviceUnitFile *parser.UnitFile, groupName string, unitsInfoMap map[string]*UnitInfo, podman *PodmanCmdline) error { pod, ok := quadletUnitFile.Lookup(groupName, KeyPod) if ok && len(pod) > 0 { + // XXX: only %N is handled. + // it is difficult to properly implement specifiers handling without consulting systemd. + pod = strings.ReplaceAll(pod, "%N", GetContainerServiceName(quadletUnitFile)) + if !strings.HasSuffix(pod, ".pod") { return fmt.Errorf("pod %s is not Quadlet based", pod) } diff --git a/test/e2e/quadlet/pod-specifier-svc.pod b/test/e2e/quadlet/pod-specifier-svc.pod new file mode 100644 index 00000000000..66ae61bee5f --- /dev/null +++ b/test/e2e/quadlet/pod-specifier-svc.pod @@ -0,0 +1 @@ +[Pod] diff --git a/test/e2e/quadlet/podspecifier.container b/test/e2e/quadlet/podspecifier.container new file mode 100644 index 00000000000..4d1a7074662 --- /dev/null +++ b/test/e2e/quadlet/podspecifier.container @@ -0,0 +1,7 @@ +## assert-podman-args "--pod" "systemd-podspecifier" +## assert-last-key-contains "Unit" "BindsTo" "podspecifier-pod.service" +## assert-last-key-contains "Unit" "After" "podspecifier-pod.service" + +[Container] +Image=localhost/imagename +Pod=%N.pod diff --git a/test/e2e/quadlet/podspecifier.pod b/test/e2e/quadlet/podspecifier.pod new file mode 100644 index 00000000000..66ae61bee5f --- /dev/null +++ b/test/e2e/quadlet/podspecifier.pod @@ -0,0 +1 @@ +[Pod] diff --git a/test/e2e/quadlet/podspecifier.servicename.container b/test/e2e/quadlet/podspecifier.servicename.container new file mode 100644 index 00000000000..968bc336718 --- /dev/null +++ b/test/e2e/quadlet/podspecifier.servicename.container @@ -0,0 +1,8 @@ +## assert-podman-args "--pod" "systemd-pod-specifier-svc" +## assert-last-key-contains "Unit" "BindsTo" "pod-specifier-svc-pod.service" +## assert-last-key-contains "Unit" "After" "pod-specifier-svc-pod.service" + +[Container] +Image=localhost/imagename +ServiceName=pod-specifier-svc +Pod=%N.pod diff --git a/test/e2e/quadlet_test.go b/test/e2e/quadlet_test.go index 25ad9577664..acaddd43986 100644 --- a/test/e2e/quadlet_test.go +++ b/test/e2e/quadlet_test.go @@ -1238,6 +1238,8 @@ BOGUS=foo Entry("Container - Mount overriding service name", "mount.servicename.container", []string{"service-name.volume"}), Entry("Container - Quadlet Network overriding service name", "network.quadlet.servicename.container", []string{"service-name.network"}), Entry("Container - Quadlet Volume overriding service name", "volume.servicename.container", []string{"service-name.volume"}), + Entry("Container - Pod with %N specifier", "podspecifier.container", []string{"podspecifier.pod"}), + Entry("Container - Pod with %N specifier and ServiceName", "podspecifier.servicename.container", []string{"pod-specifier-svc.pod"}), Entry("Container - Quadlet build with multiple tags", "build.multiple-tags.container", []string{"multiple-tags.build"}), Entry("Container - Artifact Mount", "artifact-mount.container", []string{"basic.artifact"}), Entry("Container - Reuse another container's network", "network.reuse.container", []string{"basic.container"}),