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":