Skip to content

Commit ef584d4

Browse files
Merge pull request #27169 from ygalblum/quadlet-template-dependency
Quadlet - Support template dependency
2 parents 0a58e05 + 88bca78 commit ef584d4

File tree

7 files changed

+162
-13
lines changed

7 files changed

+162
-13
lines changed

pkg/systemd/quadlet/quadlet.go

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -908,6 +908,19 @@ func getContainerName(container *parser.UnitFile) string {
908908
return containerName
909909
}
910910

911+
// Get the unresolved resource name that may contain '%'.
912+
func getResourceName(unit *parser.UnitFile, group, key string) string {
913+
resourceName, ok := unit.Lookup(group, key)
914+
if !ok || len(resourceName) == 0 {
915+
resourceName = removeExtension(unit.Filename, "systemd-", "")
916+
// By default, We want to name the resource by the service name.
917+
if strings.Contains(unit.Filename, "@") {
918+
resourceName = resourceName[:len(resourceName)-1] + "-%i"
919+
}
920+
}
921+
return resourceName
922+
}
923+
911924
// Get the resolved container name that contains no '%'.
912925
// Returns an empty string if not resolvable.
913926
func GetContainerResourceName(container *parser.UnitFile) string {
@@ -954,10 +967,7 @@ func ConvertNetwork(network *parser.UnitFile, name string, unitsInfoMap map[stri
954967
}
955968

956969
// Derive network name from unit name (with added prefix), or use user-provided name.
957-
networkName, ok := network.Lookup(NetworkGroup, KeyNetworkName)
958-
if !ok || len(networkName) == 0 {
959-
networkName = removeExtension(name, "systemd-", "")
960-
}
970+
networkName := getResourceName(network, NetworkGroup, KeyNetworkName)
961971

962972
if network.LookupBooleanWithDefault(NetworkGroup, KeyNetworkDeleteOnStop, false) {
963973
serviceStopPostCmd := createBasePodmanCommand(network, NetworkGroup)
@@ -1046,10 +1056,7 @@ func ConvertVolume(volume *parser.UnitFile, name string, unitsInfoMap map[string
10461056
}
10471057

10481058
// Derive volume name from unit name (with added prefix), or use user-provided name.
1049-
volumeName, ok := volume.Lookup(VolumeGroup, KeyVolumeName)
1050-
if !ok || len(volumeName) == 0 {
1051-
volumeName = removeExtension(name, "systemd-", "")
1052-
}
1059+
volumeName := getResourceName(volume, VolumeGroup, KeyVolumeName)
10531060

10541061
podman := createBasePodmanCommand(volume, VolumeGroup)
10551062

@@ -1503,7 +1510,12 @@ func getServiceName(quadletUnitFile *parser.UnitFile, groupName string, defaultE
15031510
if serviceName, ok := quadletUnitFile.Lookup(groupName, KeyServiceName); ok {
15041511
return serviceName
15051512
}
1506-
return removeExtension(quadletUnitFile.Filename, "", defaultExtraSuffix)
1513+
baseServiceName := removeExtension(quadletUnitFile.Filename, "", "")
1514+
if baseServiceName[len(baseServiceName)-1] == '@' {
1515+
baseServiceName = baseServiceName[:len(baseServiceName)-1]
1516+
defaultExtraSuffix += "@"
1517+
}
1518+
return baseServiceName + defaultExtraSuffix
15071519
}
15081520

15091521
func GetPodResourceName(podUnit *parser.UnitFile) string {
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
## assert-podman-args-key-val "--mount" "," "type=volume,source=systemd-template-dependency-%i,destination=/path/in/container,ro=true"
2+
## assert-podman-args -v systemd-template-dependency-%i:/container/quadlet
3+
## assert-podman-args "--network" "systemd-template-dependency-%i"
4+
## assert-key-is "Unit" "Requires" "[email protected]" "[email protected]" "[email protected]"
5+
6+
[Container]
7+
Image=localhost/imagename
8+
Mount=type=volume,[email protected],destination=/path/in/container,ro=true
9+
[email protected]:/container/quadlet
10+
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[Network]
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[Volume]

test/e2e/quadlet_test.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1195,6 +1195,14 @@ BOGUS=foo
11951195
"basic.volume",
11961196
},
11971197
),
1198+
Entry(
1199+
"Container - Template with Volume Template dependency",
1200+
1201+
[]string{
1202+
1203+
1204+
},
1205+
),
11981206

11991207
Entry("Volume - Quadlet image (.build)", "build.quadlet.volume", []string{"basic.build"}),
12001208
Entry("Volume - Quadlet image (.image)", "image.quadlet.volume", []string{"basic.image"}),

test/system/252-quadlet.bats

Lines changed: 113 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ load helpers.registry
1111
load helpers.systemd
1212

1313
UNIT_FILES=()
14+
SERVICES_TO_STOP=()
1415

1516
function start_time() {
1617
sleep_to_next_second # Ensure we're on a new second with no previous logging
@@ -26,16 +27,32 @@ function setup() {
2627

2728
start_time
2829

30+
# Clear arrays for each test
31+
SERVICES_TO_STOP=()
32+
2933
basic_setup
3034
}
3135

3236
function teardown() {
37+
# Stop manually specified services
38+
for service in ${SERVICES_TO_STOP[@]}; do
39+
run systemctl stop "$service"
40+
if [ $status -ne 0 ]; then
41+
echo "# WARNING: systemctl stop failed in teardown: $output" >&3
42+
fi
43+
run systemctl reset-failed "$service"
44+
done
45+
3346
for UNIT_FILE in ${UNIT_FILES[@]}; do
3447
if [[ -e "$UNIT_FILE" ]]; then
3548
local service=$(basename "$UNIT_FILE")
36-
run systemctl stop "$service"
37-
if [ $status -ne 0 ]; then
38-
echo "# WARNING: systemctl stop failed in teardown: $output" >&3
49+
# Skip stopping template services (those ending with '@')
50+
# as they cannot be stopped directly without an instance name
51+
if [[ ! "$service" =~ @\.service$ ]]; then
52+
run systemctl stop "$service"
53+
if [ $status -ne 0 ]; then
54+
echo "# WARNING: systemctl stop failed in teardown: $output" >&3
55+
fi
3956
fi
4057
run systemctl reset-failed "$service"
4158
rm -f "$UNIT_FILE"
@@ -437,6 +454,99 @@ EOF
437454
run_podman volume rm $volume_name
438455
}
439456

457+
# A quadlet container template depends on a quadlet volume and network templates
458+
@test "quadlet - template dependency" {
459+
# Save the unit name to use as the volume template for the container template
460+
local quadlet_vol_unit=dep_$(safename)@.volume
461+
local quadlet_vol_file=$PODMAN_TMPDIR/${quadlet_vol_unit}
462+
cat > $quadlet_vol_file <<EOF
463+
[Volume]
464+
EOF
465+
466+
local quadlet_tmpdir=$(mktemp -d --tmpdir=$PODMAN_TMPDIR quadlet.XXXXXX)
467+
# Have quadlet create the systemd unit file for the volume template unit
468+
run_quadlet "$quadlet_vol_file" "$quadlet_tmpdir"
469+
470+
# Save the volume service name since the variable will be overwritten
471+
local vol_service=$QUADLET_SERVICE_NAME
472+
local volume_name=systemd-$(basename $quadlet_vol_file .volume)
473+
# For template units, the volume name should have -%i appended
474+
volume_name=${volume_name%@}-%i
475+
476+
# Save the unit name to use as the network template for the container template
477+
local quadlet_net_unit=dep_$(safename)@.network
478+
local quadlet_net_file=$PODMAN_TMPDIR/${quadlet_net_unit}
479+
cat > $quadlet_net_file <<EOF
480+
[Network]
481+
EOF
482+
483+
# Have quadlet create the systemd unit file for the network template unit
484+
run_quadlet "$quadlet_net_file" "$quadlet_tmpdir"
485+
486+
# Save the network service name since the variable will be overwritten
487+
local net_service=$QUADLET_SERVICE_NAME
488+
local network_name=systemd-$(basename $quadlet_net_file .network)
489+
# For template units, the network name should have -%i appended
490+
network_name=${network_name%@}-%i
491+
492+
local quadlet_file=$PODMAN_TMPDIR/user_$(safename)@.container
493+
cat > $quadlet_file <<EOF
494+
[Container]
495+
Image=$IMAGE
496+
Exec=top
497+
Volume=$quadlet_vol_unit:/tmp
498+
Network=$quadlet_net_unit
499+
EOF
500+
501+
# Have quadlet create the systemd unit file for the container template unit
502+
run_quadlet "$quadlet_file" "$quadlet_tmpdir"
503+
504+
# Save the container service name for readability
505+
local container_service=$QUADLET_SERVICE_NAME
506+
507+
# Create instance names for the template units
508+
local instance_name="test"
509+
local vol_service_instance="${vol_service%@*}@${instance_name}.service"
510+
local net_service_instance="${net_service%@*}@${instance_name}.service"
511+
local container_service_instance="${container_service%@*}@${instance_name}.service"
512+
local volume_name_instance="systemd-dep_$(safename)-${instance_name}"
513+
local network_name_instance="systemd-dep_$(safename)-${instance_name}"
514+
515+
# Volume should not exist
516+
run_podman 1 volume exists ${volume_name_instance}
517+
# Network should not exist
518+
run_podman 1 network exists ${network_name_instance}
519+
520+
# Start the container service instance which should also trigger the start of the volume service instance
521+
service_setup $container_service_instance
522+
523+
# Add the service instances to SERVICES_TO_STOP for proper cleanup
524+
# SERVICES_TO_STOP+=("$container_service_instance")
525+
SERVICES_TO_STOP+=("$vol_service_instance")
526+
SERVICES_TO_STOP+=("$net_service_instance")
527+
528+
# Volume system unit instance should be active
529+
run systemctl show --property=ActiveState "$vol_service_instance"
530+
assert "$output" = "ActiveState=active" \
531+
"volume template instance should be active via dependency"
532+
533+
# Network system unit instance should be active
534+
run systemctl show --property=ActiveState "$net_service_instance"
535+
assert "$output" = "ActiveState=active" \
536+
"network template instance should be active via dependency"
537+
538+
# Volume should exist
539+
run_podman volume exists ${volume_name_instance}
540+
541+
# Network should exist
542+
run_podman network exists ${network_name_instance}
543+
544+
# Clean up the created resources
545+
service_cleanup $container_service_instance failed
546+
run_podman volume rm $volume_name_instance
547+
run_podman network rm $network_name_instance
548+
}
549+
440550
# A quadlet container depends on a named quadlet volume
441551
@test "quadlet - named volume dependency" {
442552
local volume_name="v-$(safename)"

test/system/helpers.systemd.bash

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,13 @@ quadlet_to_service_name() {
115115
local extension="${filename##*.}"
116116
local filename="${filename%.*}"
117117
local suffix=""
118+
local is_template=""
119+
120+
# Check if this is a template unit (ends with @)
121+
if [[ "$filename" == *@ ]]; then
122+
is_template="@"
123+
filename="${filename%@}"
124+
fi
118125

119126
if [ "$extension" == "volume" ]; then
120127
suffix="-volume"
@@ -128,5 +135,5 @@ quadlet_to_service_name() {
128135
suffix="-build"
129136
fi
130137

131-
echo "$filename$suffix.service"
138+
echo "$filename$suffix$is_template.service"
132139
}

0 commit comments

Comments
 (0)