From 23057fd5ed94f8733e43a32510853d5ba7648d8e Mon Sep 17 00:00:00 2001 From: Dmitry Konishchev Date: Mon, 27 Oct 2025 19:23:51 +0300 Subject: [PATCH 1/3] Rename misleading assertion name Signed-off-by: Dmitry Konishchev --- test/e2e/quadlet/device-bind.volume | 8 ++++---- test/e2e/quadlet/device-copy.volume | 8 ++++---- test/e2e/quadlet/device.volume | 8 ++++---- test/e2e/quadlet/ipv6.network | 2 +- test/e2e/quadlet/label.volume | 6 +++--- test/e2e/quadlet/startwithpod.pod | 8 ++++---- test/e2e/quadlet/startwithpod_no.container | 4 ++-- test/e2e/quadlet/startwithpod_yes.container | 4 ++-- test/e2e/quadlet/uid.volume | 4 ++-- test/e2e/quadlet_test.go | 14 +++++++------- 10 files changed, 33 insertions(+), 33 deletions(-) diff --git a/test/e2e/quadlet/device-bind.volume b/test/e2e/quadlet/device-bind.volume index 9b68ee9cd6..2b4c6bb70e 100644 --- a/test/e2e/quadlet/device-bind.volume +++ b/test/e2e/quadlet/device-bind.volume @@ -1,7 +1,7 @@ -## assert-key-contains Service ExecStart " --opt type=bind " -## assert-key-contains Service ExecStart " --opt device=/var/lib/data " -## assert-key-contains Service ExecStart " --opt nocopy " -## assert-key-contains Unit RequiresMountsFor "/var/lib/data" +## assert-last-key-contains Service ExecStart " --opt type=bind " +## assert-last-key-contains Service ExecStart " --opt device=/var/lib/data " +## assert-last-key-contains Service ExecStart " --opt nocopy " +## assert-last-key-contains Unit RequiresMountsFor "/var/lib/data" [Volume] Device=/var/lib/data diff --git a/test/e2e/quadlet/device-copy.volume b/test/e2e/quadlet/device-copy.volume index 0e7405e222..05cc6315b4 100644 --- a/test/e2e/quadlet/device-copy.volume +++ b/test/e2e/quadlet/device-copy.volume @@ -1,7 +1,7 @@ -## assert-key-contains Service ExecStart " --opt o=uid=0,gid=11,rw,compress=zstd " -## assert-key-contains Service ExecStart " --opt type=btrfs " -## assert-key-contains Service ExecStart " --opt device=/dev/vda1 " -## assert-key-contains Service ExecStart " --opt copy " +## assert-last-key-contains Service ExecStart " --opt o=uid=0,gid=11,rw,compress=zstd " +## assert-last-key-contains Service ExecStart " --opt type=btrfs " +## assert-last-key-contains Service ExecStart " --opt device=/dev/vda1 " +## assert-last-key-contains Service ExecStart " --opt copy " [Volume] # Test usernames too diff --git a/test/e2e/quadlet/device.volume b/test/e2e/quadlet/device.volume index 4e742a38c4..9b19987fe5 100644 --- a/test/e2e/quadlet/device.volume +++ b/test/e2e/quadlet/device.volume @@ -1,7 +1,7 @@ -## assert-key-contains Service ExecStart " --opt o=uid=0,gid=11,rw,compress=zstd " -## assert-key-contains Service ExecStart " --opt type=btrfs " -## assert-key-contains Service ExecStart " --opt device=/dev/vda1 " -## assert-key-contains Service ExecStart " --opt nocopy " +## assert-last-key-contains Service ExecStart " --opt o=uid=0,gid=11,rw,compress=zstd " +## assert-last-key-contains Service ExecStart " --opt type=btrfs " +## assert-last-key-contains Service ExecStart " --opt device=/dev/vda1 " +## assert-last-key-contains Service ExecStart " --opt nocopy " [Volume] # Test usernames too diff --git a/test/e2e/quadlet/ipv6.network b/test/e2e/quadlet/ipv6.network index 8a6d5d62cc..fc97591ba4 100644 --- a/test/e2e/quadlet/ipv6.network +++ b/test/e2e/quadlet/ipv6.network @@ -2,7 +2,7 @@ ## assert-podman-args "--ipv6" ## assert-key-is Service Type exec ## assert-key-is Service RemainAfterExit no -## assert-key-contains Service SyslogIdentifier "Modify %N" +## assert-last-key-contains Service SyslogIdentifier "Modify %N" [Network] IPv6=yes diff --git a/test/e2e/quadlet/label.volume b/test/e2e/quadlet/label.volume index da6ee25209..53c7eb9270 100644 --- a/test/e2e/quadlet/label.volume +++ b/test/e2e/quadlet/label.volume @@ -1,6 +1,6 @@ -## assert-key-contains Service ExecStart " --label org.foo.Arg1=arg1 " -## assert-key-contains Service ExecStart " --label org.foo.Arg2=arg2 " -## assert-key-contains Service ExecStart " --label org.foo.Arg3=arg3 " +## assert-last-key-contains Service ExecStart " --label org.foo.Arg1=arg1 " +## assert-last-key-contains Service ExecStart " --label org.foo.Arg2=arg2 " +## assert-last-key-contains Service ExecStart " --label org.foo.Arg3=arg3 " [Volume] Label=org.foo.Arg1=arg1 diff --git a/test/e2e/quadlet/startwithpod.pod b/test/e2e/quadlet/startwithpod.pod index 75505f9e55..d5a4ab042c 100644 --- a/test/e2e/quadlet/startwithpod.pod +++ b/test/e2e/quadlet/startwithpod.pod @@ -1,7 +1,7 @@ -## assert-key-contains "Unit" "Wants" "startwithpod_yes.service" -## assert-key-contains "Unit" "Before" "startwithpod_yes.service" +## assert-last-key-contains "Unit" "Wants" "startwithpod_yes.service" +## assert-last-key-contains "Unit" "Before" "startwithpod_yes.service" -## assert-key-not-contains "Unit" "Wants" "startwithpod_no.service" -## assert-key-not-contains "Unit" "Before" "startwithpod_no.service" +## assert-last-key-not-contains "Unit" "Wants" "startwithpod_no.service" +## assert-last-key-not-contains "Unit" "Before" "startwithpod_no.service" [Pod] diff --git a/test/e2e/quadlet/startwithpod_no.container b/test/e2e/quadlet/startwithpod_no.container index 0c07cfdb03..37b5c17afe 100644 --- a/test/e2e/quadlet/startwithpod_no.container +++ b/test/e2e/quadlet/startwithpod_no.container @@ -1,5 +1,5 @@ -# assert-key-contains "Unit" "After" "startwithpod-pod.service" -# assert-key-contains "Unit" "BindsTo" "startwithpod-pod.service" +# assert-last-key-contains "Unit" "After" "startwithpod-pod.service" +# assert-last-key-contains "Unit" "BindsTo" "startwithpod-pod.service" [Container] Image=localhost/image diff --git a/test/e2e/quadlet/startwithpod_yes.container b/test/e2e/quadlet/startwithpod_yes.container index d5c608f266..5b95ad0e11 100644 --- a/test/e2e/quadlet/startwithpod_yes.container +++ b/test/e2e/quadlet/startwithpod_yes.container @@ -1,5 +1,5 @@ -# assert-key-contains "Unit" "After" "startwithpod-pod.service" -# assert-key-contains "Unit" "BindsTo" "startwithpod-pod.service" +# assert-last-key-contains "Unit" "After" "startwithpod-pod.service" +# assert-last-key-contains "Unit" "BindsTo" "startwithpod-pod.service" [Container] Image=localhost/image diff --git a/test/e2e/quadlet/uid.volume b/test/e2e/quadlet/uid.volume index aa6d8171c5..8097c88620 100644 --- a/test/e2e/quadlet/uid.volume +++ b/test/e2e/quadlet/uid.volume @@ -1,7 +1,7 @@ -## assert-key-contains Service ExecStart " --opt o=uid=0,gid=11 " +## assert-last-key-contains Service ExecStart " --opt o=uid=0,gid=11 " ## assert-key-is Service Type oneshot ## assert-key-is Service RemainAfterExit no -## assert-key-contains Service SyslogIdentifier "Modify %N" +## assert-last-key-contains Service SyslogIdentifier "Modify %N" [Volume] # Test usernames too diff --git a/test/e2e/quadlet_test.go b/test/e2e/quadlet_test.go index 77e331fd00..0ca40028a5 100644 --- a/test/e2e/quadlet_test.go +++ b/test/e2e/quadlet_test.go @@ -219,7 +219,7 @@ func (t *quadletTestcase) assertLastKeyIsRegex(args []string, unit *parser.UnitF return true } -func (t *quadletTestcase) assertKeyContains(args []string, unit *parser.UnitFile) bool { +func (t *quadletTestcase) assertLastKeyContains(args []string, unit *parser.UnitFile) bool { Expect(args).To(HaveLen(3)) group := args[0] key := args[1] @@ -229,8 +229,8 @@ func (t *quadletTestcase) assertKeyContains(args []string, unit *parser.UnitFile return ok && strings.Contains(realValue, value) } -func (t *quadletTestcase) assertKeyNotContains(args []string, unit *parser.UnitFile) bool { - return !t.assertKeyContains(args, unit) +func (t *quadletTestcase) assertLastKeyNotContains(args []string, unit *parser.UnitFile) bool { + return !t.assertLastKeyContains(args, unit) } func (t *quadletTestcase) assertPodmanArgs(args []string, unit *parser.UnitFile, key string, allowRegex, globalOnly bool) bool { @@ -544,10 +544,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": + ok = t.assertLastKeyNotContains(args, unit) case "assert-last-key-is-regex": ok = t.assertLastKeyIsRegex(args, unit) case "assert-podman-args": From c8ba67f6b9a37806cd1dbb525ff013766f846985 Mon Sep 17 00:00:00 2001 From: Dmitry Konishchev Date: Mon, 27 Oct 2025 19:57:35 +0300 Subject: [PATCH 2/3] Introduce assert-has-key assertion Signed-off-by: Dmitry Konishchev --- test/e2e/quadlet_test.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/e2e/quadlet_test.go b/test/e2e/quadlet_test.go index 0ca40028a5..c690c10528 100644 --- a/test/e2e/quadlet_test.go +++ b/test/e2e/quadlet_test.go @@ -9,6 +9,7 @@ import ( "path/filepath" "reflect" "regexp" + "slices" "strings" "github.com/containers/podman/v6/pkg/systemd/parser" @@ -172,6 +173,16 @@ func (t *quadletTestcase) assertKeyIs(args []string, unit *parser.UnitFile) bool return true } +func (t *quadletTestcase) assertHasKey(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) + return slices.Contains(realValues, value) +} + func (t *quadletTestcase) assertKeyIsEmpty(args []string, unit *parser.UnitFile) bool { Expect(args).To(HaveLen(2)) group := args[0] @@ -540,6 +551,8 @@ func (t *quadletTestcase) doAssert(check []string, unit *parser.UnitFile, sessio ok = t.assertStdErrContains(args, session) case "assert-key-is": ok = t.assertKeyIs(args, unit) + case "assert-has-key": + ok = t.assertHasKey(args, unit) case "assert-key-is-empty": ok = t.assertKeyIsEmpty(args, unit) case "assert-key-is-regex": From 601a072b5104c0019c3c6658fb7950ea6841ef14 Mon Sep 17 00:00:00 2001 From: Dmitry Konishchev Date: Mon, 27 Oct 2025 20:01:45 +0300 Subject: [PATCH 3/3] Escape RequiresMountsFor value Signed-off-by: Dmitry Konishchev --- pkg/systemd/parser/unitfile.go | 9 +++++++++ pkg/systemd/quadlet/quadlet.go | 4 ++-- test/e2e/quadlet/mount.container | 4 ++++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/pkg/systemd/parser/unitfile.go b/pkg/systemd/parser/unitfile.go index 099145f592..19556e0cd7 100644 --- a/pkg/systemd/parser/unitfile.go +++ b/pkg/systemd/parser/unitfile.go @@ -879,6 +879,15 @@ func (f *UnitFile) Add(groupName string, key string, value string) { group.add(key, value) } +func (f *UnitFile) AddEscaped(groupName string, key string, value string) { + if wordNeedEscape(value) { + var escaped strings.Builder + appendEscapeWord(&escaped, value) + value = escaped.String() + } + f.Add(groupName, key, value) +} + func (f *UnitFile) AddCmdline(groupName string, key string, args []string) { f.Add(groupName, key, escapeWords(args)) } diff --git a/pkg/systemd/quadlet/quadlet.go b/pkg/systemd/quadlet/quadlet.go index 28ac14af96..6c11dd98fb 100644 --- a/pkg/systemd/quadlet/quadlet.go +++ b/pkg/systemd/quadlet/quadlet.go @@ -1151,7 +1151,7 @@ func ConvertVolume(volume *parser.UnitFile, unitsInfoMap map[string]*UnitInfo, i if devValid { podman.add("--opt", fmt.Sprintf("type=%s", devType)) if devType == "bind" { - service.Add(UnitGroup, "RequiresMountsFor", dev) + service.AddEscaped(UnitGroup, "RequiresMountsFor", dev) } } else { return nil, warnings, fmt.Errorf("key Type can't be used without Device") @@ -1929,7 +1929,7 @@ func handleStorageSource(quadletUnitFile, serviceUnitFile *parser.UnitFile, sour } if source[0] == '/' { // Absolute path - serviceUnitFile.Add(UnitGroup, "RequiresMountsFor", source) + serviceUnitFile.AddEscaped(UnitGroup, "RequiresMountsFor", source) } else if strings.HasSuffix(source, ".volume") || (checkImage && strings.HasSuffix(source, ".image")) || strings.HasSuffix(source, ".artifact") { sourceUnitInfo, ok := unitsInfoMap[source] if !ok { diff --git a/test/e2e/quadlet/mount.container b/test/e2e/quadlet/mount.container index b0a4713785..2327cf8cf3 100644 --- a/test/e2e/quadlet/mount.container +++ b/test/e2e/quadlet/mount.container @@ -1,7 +1,11 @@ [Container] Image=localhost/imagename +## assert-has-key Unit RequiresMountsFor "/path/on/host" ## assert-podman-args-key-val "--mount" "," "type=bind,source=/path/on/host,destination=/path/in/container" Mount=type=bind,source=/path/on/host,destination=/path/in/container +## assert-has-key Unit RequiresMountsFor "\"/path/on/host\\x20with\\x20spaces\"" +## assert-podman-args-key-val "--mount" "," "type=bind,source=/path/on/host with spaces,dst=/path" +Mount="type=bind,src=/path/on/host with spaces,dst=/path" ## assert-podman-args-key-val "--mount" "," "type=bind,source=/path/on/host,dst=/path/in/container,relabel=shared" Mount=type=bind,src=/path/on/host,dst=/path/in/container,relabel=shared ## assert-podman-args-key-val "--mount" "," "type=bind,source=/path/on/host,dst=/path/in/container,relabel=shared,U=true"