Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 21 additions & 9 deletions pkg/systemd/quadlet/quadlet.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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, "", "")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no chance this could be empty is there?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good thinking. But. from what I can tell (and test), it won't.
What I'm thinking is that this theoretically could happen if the file had the form of .network (or any other quadlet unit extension). But, if you look at the implementation of removeExtenstion:

func removeExtension(name string, extraPrefix string, extraSuffix string) string {

You can see that it trims the extension only the last dot is not the first character. If it is (which is my case), it is not trimmed and since I'm passing two empty strings, I get the same value back.
Can you think of a different input that could have returned an empty string?

if baseServiceName[len(baseServiceName)-1] == '@' {
baseServiceName = baseServiceName[:len(baseServiceName)-1]
defaultExtraSuffix += "@"
}
return baseServiceName + defaultExtraSuffix
}

func GetPodResourceName(podUnit *parser.UnitFile) string {
Expand Down
10 changes: 10 additions & 0 deletions test/e2e/quadlet/[email protected]
Original file line number Diff line number Diff line change
@@ -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" "[email protected]" "[email protected]" "[email protected]"

[Container]
Image=localhost/imagename
Mount=type=volume,[email protected],destination=/path/in/container,ro=true
[email protected]:/container/quadlet
[email protected]
1 change: 1 addition & 0 deletions test/e2e/quadlet/[email protected]
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[Network]
1 change: 1 addition & 0 deletions test/e2e/quadlet/[email protected]
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[Volume]
8 changes: 8 additions & 0 deletions test/e2e/quadlet_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1195,6 +1195,14 @@ BOGUS=foo
"basic.volume",
},
),
Entry(
"Container - Template with Volume Template dependency",
"[email protected]",
[]string{
"[email protected]",
"[email protected]",
},
),

Entry("Volume - Quadlet image (.build)", "build.quadlet.volume", []string{"basic.build"}),
Entry("Volume - Quadlet image (.image)", "image.quadlet.volume", []string{"basic.image"}),
Expand Down
116 changes: 113 additions & 3 deletions test/system/252-quadlet.bats
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ load helpers.registry
load helpers.systemd

UNIT_FILES=()
SERVICES_TO_STOP=()

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

start_time

# Clear arrays for each test
SERVICES_TO_STOP=()

basic_setup
}

function teardown() {
# Stop manually specified services
for service in ${SERVICES_TO_STOP[@]}; do
run systemctl stop "$service"
if [ $status -ne 0 ]; then
echo "# WARNING: systemctl stop failed in teardown: $output" >&3
fi
run systemctl reset-failed "$service"
done

for UNIT_FILE in ${UNIT_FILES[@]}; do
if [[ -e "$UNIT_FILE" ]]; then
local service=$(basename "$UNIT_FILE")
run systemctl stop "$service"
if [ $status -ne 0 ]; then
echo "# WARNING: systemctl stop failed in teardown: $output" >&3
# Skip stopping template services (those ending with '@')
# as they cannot be stopped directly without an instance name
if [[ ! "$service" =~ @\.service$ ]]; then
run systemctl stop "$service"
if [ $status -ne 0 ]; then
echo "# WARNING: systemctl stop failed in teardown: $output" >&3
fi
fi
run systemctl reset-failed "$service"
rm -f "$UNIT_FILE"
Expand Down Expand Up @@ -437,6 +454,99 @@ 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

# Add the service instances to SERVICES_TO_STOP for proper cleanup
# SERVICES_TO_STOP+=("$container_service_instance")
SERVICES_TO_STOP+=("$vol_service_instance")
SERVICES_TO_STOP+=("$net_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}

# Clean up the created resources
service_cleanup $container_service_instance failed
run_podman volume rm $volume_name_instance
run_podman network rm $network_name_instance
}

# A quadlet container depends on a named quadlet volume
@test "quadlet - named volume dependency" {
local volume_name="v-$(safename)"
Expand Down
9 changes: 8 additions & 1 deletion test/system/helpers.systemd.bash
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -128,5 +135,5 @@ quadlet_to_service_name() {
suffix="-build"
fi

echo "$filename$suffix.service"
echo "$filename$suffix$is_template.service"
}