Quadlet - Support template dependency

Add support for Volumes and Networks
Add e2e and system tests

Resolves: https://github.com/containers/podman/issues/25136

Signed-off-by: Ygal Blum <ygal.blum@gmail.com>
This commit is contained in:
Ygal Blum
2025-09-26 11:52:38 -04:00
parent a118fdf4e2
commit 83e65f91a4
7 changed files with 146 additions and 10 deletions

View File

@ -908,6 +908,19 @@ func getContainerName(container *parser.UnitFile) string {
return containerName
}
// Get the unresolved resource name that may contain '%'.
func getResourceName(unit *parser.UnitFile, group, key string) string {
resourceName, ok := unit.Lookup(group, key)
if !ok || len(resourceName) == 0 {
resourceName = removeExtension(unit.Filename, "systemd-", "")
// By default, We want to name the resource by the service name.
if strings.Contains(unit.Filename, "@") {
resourceName = resourceName[:len(resourceName)-1] + "-%i"
}
}
return resourceName
}
// Get the resolved container name that contains no '%'.
// Returns an empty string if not resolvable.
func GetContainerResourceName(container *parser.UnitFile) string {
@ -954,10 +967,7 @@ func ConvertNetwork(network *parser.UnitFile, name string, unitsInfoMap map[stri
}
// Derive network name from unit name (with added prefix), or use user-provided name.
networkName, ok := network.Lookup(NetworkGroup, KeyNetworkName)
if !ok || len(networkName) == 0 {
networkName = removeExtension(name, "systemd-", "")
}
networkName := getResourceName(network, NetworkGroup, KeyNetworkName)
if network.LookupBooleanWithDefault(NetworkGroup, KeyNetworkDeleteOnStop, false) {
serviceStopPostCmd := createBasePodmanCommand(network, NetworkGroup)
@ -1046,10 +1056,7 @@ func ConvertVolume(volume *parser.UnitFile, name string, unitsInfoMap map[string
}
// Derive volume name from unit name (with added prefix), or use user-provided name.
volumeName, ok := volume.Lookup(VolumeGroup, KeyVolumeName)
if !ok || len(volumeName) == 0 {
volumeName = removeExtension(name, "systemd-", "")
}
volumeName := getResourceName(volume, VolumeGroup, KeyVolumeName)
podman := createBasePodmanCommand(volume, VolumeGroup)
@ -1503,7 +1510,12 @@ func getServiceName(quadletUnitFile *parser.UnitFile, groupName string, defaultE
if serviceName, ok := quadletUnitFile.Lookup(groupName, KeyServiceName); ok {
return serviceName
}
return removeExtension(quadletUnitFile.Filename, "", defaultExtraSuffix)
baseServiceName := removeExtension(quadletUnitFile.Filename, "", "")
if baseServiceName[len(baseServiceName)-1] == '@' {
baseServiceName = baseServiceName[:len(baseServiceName)-1]
defaultExtraSuffix += "@"
}
return baseServiceName + defaultExtraSuffix
}
func GetPodResourceName(podUnit *parser.UnitFile) string {

View File

@ -0,0 +1,10 @@
## assert-podman-args-key-val "--mount" "," "type=volume,source=systemd-template-dependency-%i,destination=/path/in/container,ro=true"
## assert-podman-args -v systemd-template-dependency-%i:/container/quadlet
## assert-podman-args "--network" "systemd-template-dependency-%i"
## assert-key-is "Unit" "Requires" "template-dependency-network@.service" "template-dependency-volume@.service" "template-dependency-volume@.service"
[Container]
Image=localhost/imagename
Mount=type=volume,source=template-dependency@.volume,destination=/path/in/container,ro=true
Volume=template-dependency@.volume:/container/quadlet
Network=template-dependency@.network

View File

@ -0,0 +1 @@
[Network]

View File

@ -0,0 +1 @@
[Volume]

View File

@ -1195,6 +1195,14 @@ BOGUS=foo
"basic.volume",
},
),
Entry(
"Container - Template with Volume Template dependency",
"template-dependency@.container",
[]string{
"template-dependency@.volume",
"template-dependency@.network",
},
),
Entry("Volume - Quadlet image (.build)", "build.quadlet.volume", []string{"basic.build"}),
Entry("Volume - Quadlet image (.image)", "image.quadlet.volume", []string{"basic.image"}),

View File

@ -437,6 +437,103 @@ EOF
run_podman volume rm $volume_name
}
# A quadlet container template depends on a quadlet volume and network templates
@test "quadlet - template dependency" {
# Save the unit name to use as the volume template for the container template
local quadlet_vol_unit=dep_$(safename)@.volume
local quadlet_vol_file=$PODMAN_TMPDIR/${quadlet_vol_unit}
cat > $quadlet_vol_file <<EOF
[Volume]
EOF
local quadlet_tmpdir=$(mktemp -d --tmpdir=$PODMAN_TMPDIR quadlet.XXXXXX)
# Have quadlet create the systemd unit file for the volume template unit
run_quadlet "$quadlet_vol_file" "$quadlet_tmpdir"
# Save the volume service name since the variable will be overwritten
local vol_service=$QUADLET_SERVICE_NAME
local volume_name=systemd-$(basename $quadlet_vol_file .volume)
# For template units, the volume name should have -%i appended
volume_name=${volume_name%@}-%i
# Save the unit name to use as the network template for the container template
local quadlet_net_unit=dep_$(safename)@.network
local quadlet_net_file=$PODMAN_TMPDIR/${quadlet_net_unit}
cat > $quadlet_net_file <<EOF
[Network]
EOF
# Have quadlet create the systemd unit file for the network template unit
run_quadlet "$quadlet_net_file" "$quadlet_tmpdir"
# Save the network service name since the variable will be overwritten
local net_service=$QUADLET_SERVICE_NAME
local network_name=systemd-$(basename $quadlet_net_file .network)
# For template units, the network name should have -%i appended
network_name=${network_name%@}-%i
local quadlet_file=$PODMAN_TMPDIR/user_$(safename)@.container
cat > $quadlet_file <<EOF
[Container]
Image=$IMAGE
Exec=top
Volume=$quadlet_vol_unit:/tmp
Network=$quadlet_net_unit
EOF
# Have quadlet create the systemd unit file for the container template unit
run_quadlet "$quadlet_file" "$quadlet_tmpdir"
# Save the container service name for readability
local container_service=$QUADLET_SERVICE_NAME
# Create instance names for the template units
local instance_name="test"
local vol_service_instance="${vol_service%@*}@${instance_name}.service"
local net_service_instance="${net_service%@*}@${instance_name}.service"
local container_service_instance="${container_service%@*}@${instance_name}.service"
local volume_name_instance="systemd-dep_$(safename)-${instance_name}"
local network_name_instance="systemd-dep_$(safename)-${instance_name}"
# Volume should not exist
run_podman 1 volume exists ${volume_name_instance}
# Network should not exist
run_podman 1 network exists ${network_name_instance}
# Start the container service instance which should also trigger the start of the volume service instance
service_setup $container_service_instance
# Volume system unit instance should be active
run systemctl show --property=ActiveState "$vol_service_instance"
assert "$output" = "ActiveState=active" \
"volume template instance should be active via dependency"
# Network system unit instance should be active
run systemctl show --property=ActiveState "$net_service_instance"
assert "$output" = "ActiveState=active" \
"network template instance should be active via dependency"
# Volume should exist
run_podman volume exists ${volume_name_instance}
# Network should exist
run_podman network exists ${network_name_instance}
# The default cleanup stops the services corresponding to the copied unit files.
# However, the test copies the template units while running their instances.
# As a result, the default cleanup fails to stop the services.
# As a workaround, stop the services and delete the unit files manually.
service_cleanup $container_service_instance failed
service_cleanup $vol_service_instance inactive
service_cleanup $net_service_instance inactive
run_podman volume rm $volume_name_instance
run_podman network rm $network_name_instance
for UNIT_FILE in ${UNIT_FILES[@]}; do
rm $UNIT_FILE
done
UNIT_FILES=()
}
# A quadlet container depends on a named quadlet volume
@test "quadlet - named volume dependency" {
local volume_name="v-$(safename)"

View File

@ -115,6 +115,13 @@ quadlet_to_service_name() {
local extension="${filename##*.}"
local filename="${filename%.*}"
local suffix=""
local is_template=""
# Check if this is a template unit (ends with @)
if [[ "$filename" == *@ ]]; then
is_template="@"
filename="${filename%@}"
fi
if [ "$extension" == "volume" ]; then
suffix="-volume"
@ -128,5 +135,5 @@ quadlet_to_service_name() {
suffix="-build"
fi
echo "$filename$suffix.service"
echo "$filename$suffix$is_template.service"
}