From 0d3c438803b44a68831d49845c634a19dce38845 Mon Sep 17 00:00:00 2001 From: Ygal Blum Date: Mon, 5 Jan 2026 10:05:48 -0500 Subject: [PATCH] Quadlet - do not link pod service units to quadlet templates When a template container unit is linked to a pod do not add its service to the pod units dependency Fixes: https://github.com/containers/podman/issues/27844 Signed-off-by: Ygal Blum --- pkg/systemd/quadlet/quadlet.go | 8 ++++- test/e2e/quadlet/startwithpod.pod | 14 ++++++--- test/e2e/quadlet/startwithpod@.container | 6 ++++ test/e2e/quadlet/startwithpod@foo.container | 6 ++++ test/e2e/quadlet_test.go | 34 ++++++++++++++++++++- 5 files changed, 62 insertions(+), 6 deletions(-) create mode 100644 test/e2e/quadlet/startwithpod@.container create mode 100644 test/e2e/quadlet/startwithpod@foo.container diff --git a/pkg/systemd/quadlet/quadlet.go b/pkg/systemd/quadlet/quadlet.go index e76f8685d0..29508bb796 100644 --- a/pkg/systemd/quadlet/quadlet.go +++ b/pkg/systemd/quadlet/quadlet.go @@ -929,6 +929,11 @@ func ConvertContainer(container *parser.UnitFile, unitsInfoMap map[string]*UnitI return service, warnings, nil } +func isTemplateUnit(unit *parser.UnitFile) bool { + base := strings.TrimSuffix(unit.Filename, filepath.Ext(unit.Filename)) + return strings.HasSuffix(base, "@") +} + // Get the unresolved container name that may contain '%'. func getContainerName(container *parser.UnitFile) string { containerName, ok := container.Lookup(ContainerGroup, KeyContainerName) @@ -2198,7 +2203,8 @@ func handlePod(quadletUnitFile, serviceUnitFile *parser.UnitFile, groupName stri // If we want to start the container with the pod, we add it to this list. // This creates corresponding Wants=/Before= statements in the pod service. - if quadletUnitFile.LookupBooleanWithDefault(groupName, KeyStartWithPod, true) { + // Do not add this for template units as dependency cannot be created for them. + if !isTemplateUnit(quadletUnitFile) && quadletUnitFile.LookupBooleanWithDefault(groupName, KeyStartWithPod, true) { podInfo.ContainersToStart = append(podInfo.ContainersToStart, serviceUnitFile.Filename) } } diff --git a/test/e2e/quadlet/startwithpod.pod b/test/e2e/quadlet/startwithpod.pod index d5a4ab042c..b9b8e448cc 100644 --- a/test/e2e/quadlet/startwithpod.pod +++ b/test/e2e/quadlet/startwithpod.pod @@ -1,7 +1,13 @@ -## assert-last-key-contains "Unit" "Wants" "startwithpod_yes.service" -## assert-last-key-contains "Unit" "Before" "startwithpod_yes.service" +## assert-key-contains "Unit" "Before" "startwithpod_yes.service" +## assert-key-contains "Unit" "Wants" "startwithpod_yes.service" -## assert-last-key-not-contains "Unit" "Wants" "startwithpod_no.service" -## assert-last-key-not-contains "Unit" "Before" "startwithpod_no.service" +## assert-key-not-contains "Unit" "Wants" "startwithpod_no.service" +## assert-key-not-contains "Unit" "Before" "startwithpod_no.service" + +## assert-key-not-contains "Unit" "Wants" "startwithpod@.service" +## assert-key-not-contains "Unit" "Before" "startwithpod@.service" + +## assert-key-contains "Unit" "Wants" "startwithpod@foo.service" +## assert-key-contains "Unit" "Before" "startwithpod@foo.service" [Pod] diff --git a/test/e2e/quadlet/startwithpod@.container b/test/e2e/quadlet/startwithpod@.container new file mode 100644 index 0000000000..6c20a08ef0 --- /dev/null +++ b/test/e2e/quadlet/startwithpod@.container @@ -0,0 +1,6 @@ +# assert-last-key-contains "Unit" "After" "startwithpod-pod.service" +# assert-last-key-contains "Unit" "BindsTo" "startwithpod-pod.service" + +[Container] +Image=localhost/image +Pod=startwithpod.pod diff --git a/test/e2e/quadlet/startwithpod@foo.container b/test/e2e/quadlet/startwithpod@foo.container new file mode 100644 index 0000000000..6c20a08ef0 --- /dev/null +++ b/test/e2e/quadlet/startwithpod@foo.container @@ -0,0 +1,6 @@ +# assert-last-key-contains "Unit" "After" "startwithpod-pod.service" +# assert-last-key-contains "Unit" "BindsTo" "startwithpod-pod.service" + +[Container] +Image=localhost/image +Pod=startwithpod.pod diff --git a/test/e2e/quadlet_test.go b/test/e2e/quadlet_test.go index 856faf5088..b45c4d7730 100644 --- a/test/e2e/quadlet_test.go +++ b/test/e2e/quadlet_test.go @@ -212,6 +212,25 @@ func (t *quadletTestcase) assertKeyIsRegex(args []string, unit *parser.UnitFile) return true } +func (t *quadletTestcase) assertKeyContains(args []string, unit *parser.UnitFile) bool { + Expect(args).To(HaveLen(3)) + group := args[0] + key := args[1] + value := args[2] + + realValues := unit.LookupAll(group, key) + for _, realValue := range realValues { + if strings.Contains(realValue, value) { + return true + } + } + return false +} + +func (t *quadletTestcase) assertKeyNotContains(args []string, unit *parser.UnitFile) bool { + return !t.assertKeyContains(args, unit) +} + func (t *quadletTestcase) assertLastKeyIsRegex(args []string, unit *parser.UnitFile) bool { Expect(len(args)).To(BeNumerically(">=", 3)) group := args[0] @@ -557,6 +576,10 @@ func (t *quadletTestcase) doAssert(check []string, unit *parser.UnitFile, sessio ok = t.assertKeyIsEmpty(args, unit) case "assert-key-is-regex": ok = t.assertKeyIsRegex(args, unit) + case "assert-key-contains": + ok = t.assertKeyContains(args, unit) + case "assert-key-not-contains": + ok = t.assertKeyNotContains(args, unit) case "assert-last-key-contains": ok = t.assertLastKeyContains(args, unit) case "assert-last-key-not-contains": @@ -1288,7 +1311,16 @@ BOGUS=foo Entry("Pod - Quadlet Volume", "volume.pod", []string{"basic.volume"}), Entry("Pod - Quadlet Network overriding service name", "network.servicename.quadlet.pod", []string{"service-name.network"}), Entry("Pod - Quadlet Volume overriding service name", "volume.servicename.pod", []string{"service-name.volume"}), - Entry("Pod - Do not autostart a container with pod", "startwithpod.pod", []string{"startwithpod_no.container", "startwithpod_yes.container"}), + Entry( + "Pod - Do not autostart a container with pod", + "startwithpod.pod", + []string{ + "startwithpod_no.container", + "startwithpod_yes.container", + "startwithpod@.container", + "startwithpod@foo.container", + }, + ), Entry( "Pod - Dependency between quadlet units", "dependent.pod",