Quadlet - Add support for adding ExecReload command

Add tests
Update man page

Signed-off-by: Ygal Blum <ygal.blum@gmail.com>
This commit is contained in:
Ygal Blum
2025-04-09 12:41:08 -04:00
parent ce7db6e455
commit fe107ff0ce
6 changed files with 113 additions and 0 deletions

View File

@ -333,6 +333,8 @@ Valid options for `[Container]` are listed below:
| Pull=never | --pull never |
| ReadOnly=true | --read-only |
| ReadOnlyTmpfs=true | --read-only-tmpfs |
| ReloadCmd=/usr/bin/command | Add ExecReload and run exec with the value |
| ReloadSignal=SIGHUP | Add ExecReload and run kill with the signal |
| Retry=5 | --retry=5 |
| RetryDelay=5s | --retry-delay=5s |
| Rootfs=/var/lib/rootfs | --rootfs /var/lib/rootfs |
@ -784,6 +786,22 @@ If enabled, makes the image read-only.
If ReadOnly is set to `true`, mount a read-write tmpfs on /dev, /dev/shm, /run, /tmp, and /var/tmp.
### `ReloadCmd=`
Add `ExecReload` line to the `Service` that runs ` podman exec` with this command in this container.
In order to execute the reload run `systemctl reload <Service>`
Mutually exclusive with `ReloadSignal`
### `ReloadSignal=`
Add `ExecReload` line to the `Service` that runs `podman kill` with this signal which sends the signal to the main container process.
In order to execute the reload run `systemctl reload <Service>`
Mutually exclusive with `ReloadCmd`
### `Retry=`
Number of times to retry the image pull when a HTTP error occurs. Equivalent to the Podman `--retry` option.

View File

@ -139,6 +139,8 @@ const (
KeyPull = "Pull"
KeyReadOnly = "ReadOnly"
KeyReadOnlyTmpfs = "ReadOnlyTmpfs"
KeyReloadCmd = "ReloadCmd"
KeyReloadSignal = "ReloadSignal"
KeyRemapGid = "RemapGid" // deprecated
KeyRemapUid = "RemapUid" // deprecated
KeyRemapUidSize = "RemapUidSize" // deprecated
@ -256,6 +258,8 @@ var (
KeyPull: true,
KeyReadOnly: true,
KeyReadOnlyTmpfs: true,
KeyReloadCmd: true,
KeyReloadSignal: true,
KeyRemapGid: true,
KeyRemapUid: true,
KeyRemapUidSize: true,
@ -578,6 +582,10 @@ func ConvertContainer(container *parser.UnitFile, isUser bool, unitsInfoMap map[
serviceStopCmd.Args[0] = fmt.Sprintf("-%s", serviceStopCmd.Args[0])
service.AddCmdline(ServiceGroup, "ExecStopPost", serviceStopCmd.Args)
if err := handleExecReload(container, service, ContainerGroup); err != nil {
return nil, warnings, err
}
podman := createBasePodmanCommand(container, ContainerGroup)
podman.add("run")
@ -2226,3 +2234,29 @@ func addDefaultDependencies(service *parser.UnitFile, isUser bool) {
service.PrependUnitLine(UnitGroup, "Wants", networkUnit)
}
}
func handleExecReload(quadletUnitFile, serviceUnitFile *parser.UnitFile, groupName string) error {
reloadSignal, signalOk := quadletUnitFile.Lookup(groupName, KeyReloadSignal)
signalOk = signalOk && len(reloadSignal) > 0
reloadcmd, cmdOk := quadletUnitFile.LookupLastArgs(groupName, KeyReloadCmd)
cmdOk = cmdOk && len(reloadcmd) > 0
if !cmdOk && !signalOk {
return nil
}
if cmdOk && signalOk {
return fmt.Errorf("%s and %s are mutually exclusive but both are set", KeyReloadCmd, KeyReloadSignal)
}
serviceReloadCmd := createBasePodmanCommand(quadletUnitFile, groupName)
if cmdOk {
serviceReloadCmd.add("exec", "--cidfile=%t/%N.cid")
serviceReloadCmd.add(reloadcmd...)
} else {
serviceReloadCmd.add("kill", "--cidfile=%t/%N.cid", "--signal", reloadSignal)
}
serviceUnitFile.AddCmdline(ServiceGroup, "ExecReload", serviceReloadCmd.Args)
return nil
}

View File

@ -0,0 +1,7 @@
## assert-failed
## assert-stderr-contains "ReloadCmd and ReloadSignal are mutually exclusive but both are set"
[Container]
Image=localhost/imagename
ReloadCmd=/usr/bin/command
ReloadSignal=SIGHUP

View File

@ -0,0 +1,8 @@
## assert-podman-reload-args "exec"
## assert-podman-reload-args "--cidfile=%t/%N.cid"
## assert-podman-reload-final-args "/some/binary file" "--arg1" "arg 2"
[Container]
Image=localhost/imagename
ReloadCmd="/some/binary file" --arg1 \
"arg 2"

View File

@ -0,0 +1,7 @@
## assert-podman-reload-args "kill"
## assert-podman-reload-args "--cidfile=%t/%N.cid"
## assert-podman-reload-args "--signal" "SIGHUP"
[Container]
Image=localhost/imagename
ReloadSignal=SIGHUP

View File

@ -477,6 +477,30 @@ func (t *quadletTestcase) assertStopPostPodmanArgsKeyValRegex(args []string, uni
return t.assertPodmanArgsKeyVal(args, unit, "ExecStopPost", true, false)
}
func (t *quadletTestcase) assertReloadPodmanArgs(args []string, unit *parser.UnitFile) bool {
return t.assertPodmanArgs(args, unit, "ExecReload", false, false)
}
func (t *quadletTestcase) assertReloadPodmanGlobalArgs(args []string, unit *parser.UnitFile) bool {
return t.assertPodmanArgs(args, unit, "ExecReload", false, true)
}
func (t *quadletTestcase) assertReloadPodmanFinalArgs(args []string, unit *parser.UnitFile) bool {
return t.assertPodmanFinalArgs(args, unit, "ExecReload")
}
func (t *quadletTestcase) assertReloadPodmanFinalArgsRegex(args []string, unit *parser.UnitFile) bool {
return t.assertPodmanFinalArgsRegex(args, unit, "ExecReload")
}
func (t *quadletTestcase) assertReloadPodmanArgsKeyVal(args []string, unit *parser.UnitFile) bool {
return t.assertPodmanArgsKeyVal(args, unit, "ExecReload", false, false)
}
func (t *quadletTestcase) assertReloadPodmanArgsKeyValRegex(args []string, unit *parser.UnitFile) bool {
return t.assertPodmanArgsKeyVal(args, unit, "ExecReload", true, false)
}
func (t *quadletTestcase) assertSymlink(args []string, unit *parser.UnitFile) bool {
Expect(args).To(HaveLen(2))
symlink := args[0]
@ -590,6 +614,18 @@ func (t *quadletTestcase) doAssert(check []string, unit *parser.UnitFile, sessio
ok = t.assertStopPostPodmanArgsKeyVal(args, unit)
case "assert-podman-stop-post-args-key-val-regex":
ok = t.assertStopPostPodmanArgsKeyValRegex(args, unit)
case "assert-podman-reload-args":
ok = t.assertReloadPodmanArgs(args, unit)
case "assert-podman-reload-global-args":
ok = t.assertReloadPodmanGlobalArgs(args, unit)
case "assert-podman-reload-final-args":
ok = t.assertReloadPodmanFinalArgs(args, unit)
case "assert-podman-reload-final-args-regex":
ok = t.assertReloadPodmanFinalArgsRegex(args, unit)
case "assert-podman-reload-args-key-val":
ok = t.assertReloadPodmanArgsKeyVal(args, unit)
case "assert-podman-reload-args-key-val-regex":
ok = t.assertReloadPodmanArgsKeyValRegex(args, unit)
default:
return fmt.Errorf("Unsupported assertion %s", op)
@ -926,6 +962,8 @@ BOGUS=foo
Entry("CgroupMode", "cgroups-mode.container"),
Entry("Container - No Default Dependencies", "no_deps.container"),
Entry("retry.container", "retry.container"),
Entry("reloadcmd.container", "reloadcmd.container"),
Entry("reloadsignal.container", "reloadsignal.container"),
Entry("basic.volume", "basic.volume"),
Entry("device-copy.volume", "device-copy.volume"),
@ -1063,6 +1101,7 @@ BOGUS=foo
Entry("pod.not-found.container", "pod.not-found.container", "converting \"pod.not-found.container\": quadlet pod unit not-found.pod does not exist"),
Entry("subidmapping-with-remap.container", "subidmapping-with-remap.container", "converting \"subidmapping-with-remap.container\": deprecated Remap keys are set along with explicit mapping keys"),
Entry("userns-with-remap.container", "userns-with-remap.container", "converting \"userns-with-remap.container\": deprecated Remap keys are set along with explicit mapping keys"),
Entry("reloadboth.container", "reloadboth.container", "converting \"reloadboth.container\": ReloadCmd and ReloadSignal are mutually exclusive but both are set"),
Entry("image-no-image.volume", "image-no-image.volume", "converting \"image-no-image.volume\": the key Image is mandatory when using the image driver"),
Entry("Volume - Quadlet image (.build) not found", "build-not-found.quadlet.volume", "converting \"build-not-found.quadlet.volume\": requested Quadlet image not-found.build was not found"),