From 5382997e01ef0326e2d34832724ff8114b0e5930 Mon Sep 17 00:00:00 2001 From: Ygal Blum Date: Mon, 20 Mar 2023 17:50:08 +0200 Subject: [PATCH] Quadlet - add support for relative path in Volume key in .container file If the volume source starts with . resolve the path relative to the location of the unit file Update the test code to allow verification of regex for the value in key value arguments Add the usage of relative paths to the volume and mount test cases Update the man page Signed-off-by: Ygal Blum --- docs/source/markdown/podman-systemd.unit.5.md | 2 ++ pkg/systemd/quadlet/quadlet.go | 31 ++++++++++++----- test/e2e/quadlet/mount.container | 2 ++ test/e2e/quadlet/volume.container | 2 ++ test/e2e/quadlet_test.go | 33 +++++++++++++++++-- 5 files changed, 59 insertions(+), 11 deletions(-) diff --git a/docs/source/markdown/podman-systemd.unit.5.md b/docs/source/markdown/podman-systemd.unit.5.md index 86e07d4352..110711c34d 100644 --- a/docs/source/markdown/podman-systemd.unit.5.md +++ b/docs/source/markdown/podman-systemd.unit.5.md @@ -467,6 +467,8 @@ If enabled, the container will have a fresh tmpfs mounted on `/tmp`. Mount a volume in the container. This is equivalent to the Podman `--volume` option, and generally has the form `[[SOURCE-VOLUME|HOST-DIR:]CONTAINER-DIR[:OPTIONS]]`. +If `SOURCE-VOLUME` starts with `.`, Quadlet will resolve the path relative to the location of the unit file. + As a special case, if `SOURCE-VOLUME` ends with `.volume`, a Podman named volume called `systemd-$name` will be used as the source, and the generated systemd service will contain a dependency on the `$name-volume.service`. Such a volume can be automatically be lazily diff --git a/pkg/systemd/quadlet/quadlet.go b/pkg/systemd/quadlet/quadlet.go index ecee2225e1..072d238b17 100644 --- a/pkg/systemd/quadlet/quadlet.go +++ b/pkg/systemd/quadlet/quadlet.go @@ -492,7 +492,11 @@ func ConvertContainer(container *parser.UnitFile, isUser bool) (*parser.UnitFile } if source != "" { - source = handleStorageSource(service, source) + var err error + source, err = handleStorageSource(container, service, source) + if err != nil { + return nil, err + } } podman.add("-v") @@ -564,10 +568,14 @@ func ConvertContainer(container *parser.UnitFile, isUser bool) (*parser.UnitFile } if paramType, ok := paramsMap["type"]; ok { if paramType == "volume" || paramType == "bind" { + var err error if paramSource, ok := paramsMap["source"]; ok { - paramsMap["source"] = handleStorageSource(service, paramSource) + paramsMap["source"], err = handleStorageSource(container, service, paramSource) } else if paramSource, ok = paramsMap["src"]; ok { - paramsMap["src"] = handleStorageSource(service, paramSource) + paramsMap["src"], err = handleStorageSource(container, service, paramSource) + } + if err != nil { + return nil, err } } } @@ -1047,10 +1055,17 @@ func handleLogDriver(unitFile *parser.UnitFile, groupName string, podman *Podman podman.add("--log-driver", logDriver) } -func handleStorageSource(unitFile *parser.UnitFile, source string) string { +func handleStorageSource(quadletUnitFile, serviceUnitFile *parser.UnitFile, source string) (string, error) { + if source[0] == '.' { + var err error + source, err = getAbsolutePath(quadletUnitFile, source) + if err != nil { + return "", err + } + } if source[0] == '/' { // Absolute path - unitFile.Add(UnitGroup, "RequiresMountsFor", source) + serviceUnitFile.Add(UnitGroup, "RequiresMountsFor", source) } else if strings.HasSuffix(source, ".volume") { // the podman volume name is systemd-$name volumeName := replaceExtension(source, "", "systemd-", "") @@ -1060,11 +1075,11 @@ func handleStorageSource(unitFile *parser.UnitFile, source string) string { source = volumeName - unitFile.Add(UnitGroup, "Requires", volumeServiceName) - unitFile.Add(UnitGroup, "After", volumeServiceName) + serviceUnitFile.Add(UnitGroup, "Requires", volumeServiceName) + serviceUnitFile.Add(UnitGroup, "After", volumeServiceName) } - return source + return source, nil } func handleHealth(unitFile *parser.UnitFile, groupName string, podman *PodmanCmdline) { diff --git a/test/e2e/quadlet/mount.container b/test/e2e/quadlet/mount.container index 097f27e071..dae7be9f2c 100644 --- a/test/e2e/quadlet/mount.container +++ b/test/e2e/quadlet/mount.container @@ -18,3 +18,5 @@ Mount=type=tmpfs,tmpfs-size=512M,destination=/path/in/container Mount=type=image,source=fedora,destination=/fedora-image,rw=true ## assert-podman-args-key-val "--mount" "," "type=devpts,destination=/dev/pts" Mount=type=devpts,destination=/dev/pts +## assert-podman-args-key-val-regex "--mount" "," "type=bind,source=.*/podman_test.*/quadlet/path/on/host,destination=/path/in/container" +Mount=type=bind,source=./path/on/host,destination=/path/in/container diff --git a/test/e2e/quadlet/volume.container b/test/e2e/quadlet/volume.container index dc1acdc14e..6484a9ff0e 100644 --- a/test/e2e/quadlet/volume.container +++ b/test/e2e/quadlet/volume.container @@ -1,5 +1,6 @@ ## assert-podman-args -v /host/dir:/container/volume ## assert-podman-args -v /host/dir2:/container/volume2:Z +## assert-podman-args-regex -v .*/podman_test.*/quadlet/host/dir3:/container/volume3 ## assert-podman-args -v named:/container/named ## assert-podman-args -v systemd-quadlet:/container/quadlet localhost/imagename @@ -7,6 +8,7 @@ Image=localhost/imagename Volume=/host/dir:/container/volume Volume=/host/dir2:/container/volume2:Z +Volume=./host/dir3:/container/volume3 Volume=/container/empty Volume=named:/container/named Volume=quadlet.volume:/container/quadlet diff --git a/test/e2e/quadlet_test.go b/test/e2e/quadlet_test.go index ddccbcfce4..52da0079a9 100644 --- a/test/e2e/quadlet_test.go +++ b/test/e2e/quadlet_test.go @@ -187,7 +187,24 @@ func keyValueStringToMap(keyValueString, separator string) map[string]string { return keyValMap } -func (t *quadletTestcase) assertPodmanArgsKeyVal(args []string, unit *parser.UnitFile, key string) bool { +func keyValMapEqualRegex(expectedKeyValMap, actualKeyValMap map[string]string) bool { + if len(expectedKeyValMap) != len(actualKeyValMap) { + return false + } + for key, expectedValue := range expectedKeyValMap { + actualValue, ok := actualKeyValMap[key] + if !ok { + return false + } + matched, err := regexp.MatchString(expectedValue, actualValue) + if err != nil || !matched { + return false + } + } + return true +} + +func (t *quadletTestcase) assertPodmanArgsKeyVal(args []string, unit *parser.UnitFile, key string, allowRegex bool) bool { podmanArgs, _ := unit.LookupLastArgs("Service", key) expectedKeyValMap := keyValueStringToMap(args[2], args[1]) @@ -200,7 +217,11 @@ func (t *quadletTestcase) assertPodmanArgsKeyVal(args []string, unit *parser.Uni argKeyLocation += subListLocation actualKeyValMap := keyValueStringToMap(podmanArgs[argKeyLocation+1], args[1]) - if reflect.DeepEqual(expectedKeyValMap, actualKeyValMap) { + if allowRegex { + if keyValMapEqualRegex(expectedKeyValMap, actualKeyValMap) { + return true + } + } else if reflect.DeepEqual(expectedKeyValMap, actualKeyValMap) { return true } @@ -239,7 +260,11 @@ func (t *quadletTestcase) assertStartPodmanArgsRegex(args []string, unit *parser } func (t *quadletTestcase) assertStartPodmanArgsKeyVal(args []string, unit *parser.UnitFile) bool { - return t.assertPodmanArgsKeyVal(args, unit, "ExecStart") + return t.assertPodmanArgsKeyVal(args, unit, "ExecStart", false) +} + +func (t *quadletTestcase) assertStartPodmanArgsKeyValRegex(args []string, unit *parser.UnitFile) bool { + return t.assertPodmanArgsKeyVal(args, unit, "ExecStart", true) } func (t *quadletTestcase) assertStartPodmanFinalArgs(args []string, unit *parser.UnitFile) bool { @@ -309,6 +334,8 @@ func (t *quadletTestcase) doAssert(check []string, unit *parser.UnitFile, sessio ok = t.assertStartPodmanArgsRegex(args, unit) case "assert-podman-args-key-val": ok = t.assertStartPodmanArgsKeyVal(args, unit) + case "assert-podman-args-key-val-regex": + ok = t.assertStartPodmanArgsKeyValRegex(args, unit) case "assert-podman-final-args": ok = t.assertStartPodmanFinalArgs(args, unit) case "assert-podman-final-args-regex":