Allow setting volume and network names in Quadlet

This commit extends `Volume` and `Network` unit definitions with two
additional parameters, `VolumeName` and `NetworkName`, which will,
respectively, set a user-defined name for the corresponding volume and
network. This is similar to how the `ContainerName` directive currently
works, and should allow for smoother transitions to Quadlet-managed
resources.

Closes: #19003
Signed-off-by: Alex Palaistras <alex@deuill.org>
This commit is contained in:
Alex Palaistras
2023-06-26 22:50:43 +01:00
parent d31b9eb9e7
commit 932fae4028
7 changed files with 329 additions and 74 deletions

View File

@ -8,6 +8,7 @@ import (
"os/user"
"path"
"path/filepath"
"sort"
"strings"
"unicode"
@ -132,19 +133,22 @@ func isExtSupported(filename string) bool {
return ok
}
func loadUnitsFromDir(sourcePath string, units map[string]*parser.UnitFile) error {
func loadUnitsFromDir(sourcePath string) ([]*parser.UnitFile, error) {
var prevError error
files, err := os.ReadDir(sourcePath)
if err != nil {
if !errors.Is(err, os.ErrNotExist) {
return err
return nil, err
}
return nil
return []*parser.UnitFile{}, nil
}
var units []*parser.UnitFile
var seen = make(map[string]struct{})
for _, file := range files {
name := file.Name()
if units[name] == nil && isExtSupported(name) {
if _, ok := seen[name]; !ok && isExtSupported(name) {
path := path.Join(sourcePath, name)
Debugf("Loading source unit file %s", path)
@ -155,11 +159,13 @@ func loadUnitsFromDir(sourcePath string, units map[string]*parser.UnitFile) erro
prevError = fmt.Errorf("%s\n%s", prevError, err)
}
} else {
units[name] = f
seen[name] = void
units = append(units, f)
}
}
}
return prevError
return units, prevError
}
func generateServiceFile(service *parser.UnitFile) error {
@ -357,10 +363,12 @@ func process() error {
sourcePaths := getUnitDirs(isUserFlag)
units := make(map[string]*parser.UnitFile)
var units []*parser.UnitFile
for _, d := range sourcePaths {
if err := loadUnitsFromDir(d, units); err != nil {
if result, err := loadUnitsFromDir(d); err != nil {
reportError(err)
} else {
units = append(units, result...)
}
}
@ -379,29 +387,44 @@ func process() error {
}
}
for name, unit := range units {
// Sort unit files according to potential inter-dependencies, with Volume and Network units
// taking precedence over all others.
sort.Slice(units, func(i, j int) bool {
name := units[i].Filename
return strings.HasSuffix(name, ".volume") || strings.HasSuffix(name, ".network")
})
// A map of network/volume unit file-names, against their calculated names, as needed by Podman.
var resourceNames = make(map[string]string)
for _, unit := range units {
var service *parser.UnitFile
var name string
var err error
switch {
case strings.HasSuffix(name, ".container"):
case strings.HasSuffix(unit.Filename, ".container"):
warnIfAmbiguousName(unit)
service, err = quadlet.ConvertContainer(unit, isUserFlag)
case strings.HasSuffix(name, ".volume"):
service, err = quadlet.ConvertVolume(unit, name)
case strings.HasSuffix(name, ".kube"):
service, err = quadlet.ConvertKube(unit, isUserFlag)
case strings.HasSuffix(name, ".network"):
service, err = quadlet.ConvertNetwork(unit, name)
service, err = quadlet.ConvertContainer(unit, resourceNames, isUserFlag)
case strings.HasSuffix(unit.Filename, ".volume"):
service, name, err = quadlet.ConvertVolume(unit, unit.Filename)
case strings.HasSuffix(unit.Filename, ".kube"):
service, err = quadlet.ConvertKube(unit, resourceNames, isUserFlag)
case strings.HasSuffix(unit.Filename, ".network"):
service, name, err = quadlet.ConvertNetwork(unit, unit.Filename)
default:
Logf("Unsupported file type %q", name)
Logf("Unsupported file type %q", unit.Filename)
continue
}
if err != nil {
reportError(fmt.Errorf("converting %q: %w", name, err))
reportError(fmt.Errorf("converting %q: %w", unit.Filename, err))
continue
}
if name != "" {
resourceNames[unit.Filename] = name
}
service.Path = path.Join(outputPath, service.Filename)
if dryRunFlag {

View File

@ -77,8 +77,9 @@ the container that is run as a service. The resulting service file contains a li
options passed to Podman. However, some options also affect the details of how systemd is set up to run and
interact with the container.
By default, the Podman container has the same name as the unit, but with a `systemd-` prefix.
I.e. a `$name.container` file creates a `$name.service` unit and a `systemd-$name` Podman container.
By default, the Podman container has the same name as the unit, but with a `systemd-` prefix, i.e.
a `$name.container` file creates a `$name.service` unit and a `systemd-$name` Podman container. The
`ContainerName` option allows for overriding this default name with a user-provided one.
There is only one required key, `Image`, which defines the container image the service runs.
@ -633,8 +634,10 @@ Network files are named with a `.network` extension and contain a section `[Netw
named Podman network. The generated service is a one-time command that ensures that the network
exists on the host, creating it if needed.
For a network file named `$NAME.network`, the generated Podman network is called `systemd-$NAME`,
and the generated service file `$NAME-network.service`.
By default, the Podman network has the same name as the unit, but with a `systemd-` prefix, i.e. for
a network file named `$NAME.network`, the generated Podman network is called `systemd-$NAME`, and
the generated service file is `$NAME-network.service`. The `NetworkName` option allows for
overriding this default name with a user-provided one.
Using network units allows containers to depend on networks being automatically pre-created. This is
particularly interesting when using special options to control network creation, as Podman otherwise creates networks with the default options.
@ -642,7 +645,7 @@ particularly interesting when using special options to control network creation,
Valid options for `[Network]` are listed below:
| **[Network] options** | **podman network create equivalent** |
| ----------------- | ------------------ |
|-------------------------------|--------------------------------------|
| DisableDNS=true | --disable-dns |
| Driver=bridge | --driver bridge |
| Gateway=192.168.55.3 | --gateway 192.168.55.3 |
@ -651,6 +654,7 @@ Valid options for `[Network]` are listed below:
| IPRange=192.168.55.128/25 | --ip-range 192.168.55.128/25 |
| IPv6=true | --ipv6 |
| Label="YXZ" | --label "XYZ" |
| NetworkName=foo | podman network create foo |
| Options=isolate | --opt isolate |
| PodmanArgs=--dns=192.168.55.1 | --dns=192.168.55.1 |
| Subnet=192.5.0.0/16 | --subnet 192.5.0.0/16 |
@ -712,6 +716,12 @@ Set one or more OCI labels on the network. The format is a list of
This key can be listed multiple times.
### `NetworkName=`
The (optional) name of the Podman network. If this is not specified, the default value of
`systemd-%N` is used, which is the same as the unit name but with a `systemd-` prefix to avoid
conflicts with user-managed networks.
### `Options=`
Set driver specific options.
@ -745,8 +755,10 @@ Volume files are named with a `.volume` extension and contain a section `[Volume
named Podman volume. The generated service is a one-time command that ensures that the volume
exists on the host, creating it if needed.
For a volume file named `$NAME.volume`, the generated Podman volume is called `systemd-$NAME`,
and the generated service file `$NAME-volume.service`.
By default, the Podman volume has the same name as the unit, but with a `systemd-` prefix, i.e. for
a volume file named `$NAME.volume`, the generated Podman volume is called `systemd-$NAME`, and the
generated service file is `$NAME-volume.service`. The `VolumeName` option allows for overriding this
default name with a user-provided one.
Using volume units allows containers to depend on volumes being automatically pre-created. This is
particularly interesting when using special options to control volume creation,
@ -755,13 +767,14 @@ as Podman otherwise creates volumes with the default options.
Valid options for `[Volume]` are listed below:
| **[Volume] options** | **podman volume create equivalent** |
| ----------------- | ------------------ |
|---------------------------|-------------------------------------|
| Device=tmpfs | --opt device=tmpfs |
| Copy=true | --opt copy |
| Group=192 | --opt group=192 |
| Label="foo=bar" | --label "foo=bar" |
| Options=XYZ | --opt XYZ |
| PodmanArgs=--driver=image | --driver=image |
| VolumeName=foo | podman volume create foo |
Supported keys in `[Volume]` section are:
@ -810,6 +823,12 @@ The filesystem type of `Device` as used by the **mount(8)** commands `-t` option
The host (numeric) UID, or user name to use as the owner for the volume
### `VolumeName=`
The (optional) name of the Podman volume. If this is not specified, the default value of
`systemd-%N` is used, which is the same as the unit name but with a `systemd-` prefix to avoid
conflicts with user-managed volumes.
## EXAMPLES
Example `test.container`:

View File

@ -80,6 +80,7 @@ const (
KeyNetworkIPRange = "IPRange"
KeyNetworkIPv6 = "IPv6"
KeyNetworkInternal = "Internal"
KeyNetworkName = "NetworkName"
KeyNetworkOptions = "Options"
KeyNetworkSubnet = "Subnet"
KeyNoNewPrivileges = "NoNewPrivileges"
@ -111,6 +112,7 @@ const (
KeyUserNS = "UserNS"
KeyVolatileTmp = "VolatileTmp"
KeyVolume = "Volume"
KeyVolumeName = "VolumeName"
KeyWorkingDir = "WorkingDir"
KeyYaml = "Yaml"
)
@ -192,6 +194,7 @@ var (
KeyPodmanArgs: true,
KeyType: true,
KeyUser: true,
KeyVolumeName: true,
}
// Supported keys in "Network" group
@ -204,6 +207,7 @@ var (
KeyNetworkIPRange: true,
KeyNetworkIPv6: true,
KeyNetworkInternal: true,
KeyNetworkName: true,
KeyNetworkOptions: true,
KeyNetworkSubnet: true,
KeyPodmanArgs: true,
@ -300,7 +304,7 @@ func usernsOpts(kind string, opts []string) string {
// service file (unit file with Service group) based on the options in the
// Container group.
// The original Container group is kept around as X-Container.
func ConvertContainer(container *parser.UnitFile, isUser bool) (*parser.UnitFile, error) {
func ConvertContainer(container *parser.UnitFile, names map[string]string, isUser bool) (*parser.UnitFile, error) {
service := container.Dup()
service.Filename = replaceExtension(container.Filename, ".service", "", "")
@ -386,7 +390,7 @@ func ConvertContainer(container *parser.UnitFile, isUser bool) (*parser.UnitFile
podman.addf("--tz=%s", timezone)
}
addNetworks(container, ContainerGroup, service, podman)
addNetworks(container, ContainerGroup, service, names, podman)
// Run with a pid1 init to reap zombies by default (as most apps don't do that)
runInit, ok := container.LookupBoolean(ContainerGroup, KeyRunInit)
@ -555,7 +559,7 @@ func ConvertContainer(container *parser.UnitFile, isUser bool) (*parser.UnitFile
if source != "" {
var err error
source, err = handleStorageSource(container, service, source)
source, err = handleStorageSource(container, service, source, names)
if err != nil {
return nil, err
}
@ -649,9 +653,9 @@ func ConvertContainer(container *parser.UnitFile, isUser bool) (*parser.UnitFile
if paramType == "volume" || paramType == "bind" {
var err error
if paramSource, ok := paramsMap["source"]; ok {
paramsMap["source"], err = handleStorageSource(container, service, paramSource)
paramsMap["source"], err = handleStorageSource(container, service, paramSource, names)
} else if paramSource, ok = paramsMap["src"]; ok {
paramsMap["src"], err = handleStorageSource(container, service, paramSource)
paramsMap["src"], err = handleStorageSource(container, service, paramSource, names)
}
if err != nil {
return nil, err
@ -702,18 +706,24 @@ func ConvertContainer(container *parser.UnitFile, isUser bool) (*parser.UnitFile
// service file (unit file with Service group) based on the options in the
// Network group.
// The original Network group is kept around as X-Network.
func ConvertNetwork(network *parser.UnitFile, name string) (*parser.UnitFile, error) {
// Also returns the canonical network name, either auto-generated or user-defined via the
// NetworkName key-value.
func ConvertNetwork(network *parser.UnitFile, name string) (*parser.UnitFile, string, error) {
service := network.Dup()
service.Filename = replaceExtension(network.Filename, ".service", "", "-network")
if err := checkForUnknownKeys(network, NetworkGroup, supportedNetworkKeys); err != nil {
return nil, err
return nil, "", err
}
/* Rename old Network group to x-Network so that systemd ignores it */
service.RenameGroup(NetworkGroup, XNetworkGroup)
networkName := replaceExtension(name, "", "systemd-", "")
// 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 = replaceExtension(name, "", "systemd-", "")
}
// Need the containers filesystem mounted to start podman
service.Add(UnitGroup, "RequiresMountsFor", "%t/containers")
@ -734,10 +744,10 @@ func ConvertNetwork(network *parser.UnitFile, name string) (*parser.UnitFile, er
ipRanges := network.LookupAll(NetworkGroup, KeyNetworkIPRange)
if len(subnets) > 0 {
if len(gateways) > len(subnets) {
return nil, fmt.Errorf("cannot set more gateways than subnets")
return nil, "", fmt.Errorf("cannot set more gateways than subnets")
}
if len(ipRanges) > len(subnets) {
return nil, fmt.Errorf("cannot set more ranges than subnets")
return nil, "", fmt.Errorf("cannot set more ranges than subnets")
}
for i := range subnets {
podman.addf("--subnet=%s", subnets[i])
@ -749,7 +759,7 @@ func ConvertNetwork(network *parser.UnitFile, name string) (*parser.UnitFile, er
}
}
} else if len(ipRanges) > 0 || len(gateways) > 0 {
return nil, fmt.Errorf("cannot set gateway or range without subnet")
return nil, "", fmt.Errorf("cannot set gateway or range without subnet")
}
if internal := network.LookupBooleanWithDefault(NetworkGroup, KeyNetworkInternal, false); internal {
@ -786,25 +796,31 @@ func ConvertNetwork(network *parser.UnitFile, name string) (*parser.UnitFile, er
// The default syslog identifier is the exec basename (podman) which isn't very useful here
"SyslogIdentifier", "%N")
return service, nil
return service, networkName, nil
}
// Convert a quadlet volume file (unit file with a Volume group) to a systemd
// service file (unit file with Service group) based on the options in the
// Volume group.
// The original Volume group is kept around as X-Volume.
func ConvertVolume(volume *parser.UnitFile, name string) (*parser.UnitFile, error) {
// Also returns the canonical volume name, either auto-generated or user-defined via the VolumeName
// key-value.
func ConvertVolume(volume *parser.UnitFile, name string) (*parser.UnitFile, string, error) {
service := volume.Dup()
service.Filename = replaceExtension(volume.Filename, ".service", "", "-volume")
if err := checkForUnknownKeys(volume, VolumeGroup, supportedVolumeKeys); err != nil {
return nil, err
return nil, "", err
}
/* Rename old Volume group to x-Volume so that systemd ignores it */
service.RenameGroup(VolumeGroup, XVolumeGroup)
volumeName := replaceExtension(name, "", "systemd-", "")
// 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 = replaceExtension(name, "", "systemd-", "")
}
// Need the containers filesystem mounted to start podman
service.Add(UnitGroup, "RequiresMountsFor", "%t/containers")
@ -854,7 +870,7 @@ func ConvertVolume(volume *parser.UnitFile, name string) (*parser.UnitFile, erro
if devValid {
podman.add("--opt", fmt.Sprintf("type=%s", devType))
} else {
return nil, fmt.Errorf("key Type can't be used without Device")
return nil, "", fmt.Errorf("key Type can't be used without Device")
}
}
@ -866,7 +882,7 @@ func ConvertVolume(volume *parser.UnitFile, name string) (*parser.UnitFile, erro
}
opts.WriteString(mountOpts)
} else {
return nil, fmt.Errorf("key Options can't be used without Device")
return nil, "", fmt.Errorf("key Options can't be used without Device")
}
}
@ -889,10 +905,10 @@ func ConvertVolume(volume *parser.UnitFile, name string) (*parser.UnitFile, erro
// The default syslog identifier is the exec basename (podman) which isn't very useful here
"SyslogIdentifier", "%N")
return service, nil
return service, volumeName, nil
}
func ConvertKube(kube *parser.UnitFile, isUser bool) (*parser.UnitFile, error) {
func ConvertKube(kube *parser.UnitFile, names map[string]string, isUser bool) (*parser.UnitFile, error) {
service := kube.Dup()
service.Filename = replaceExtension(kube.Filename, ".service", "", "")
@ -964,7 +980,7 @@ func ConvertKube(kube *parser.UnitFile, isUser bool) (*parser.UnitFile, error) {
handleUserNS(kube, KubeGroup, execStart)
addNetworks(kube, KubeGroup, service, execStart)
addNetworks(kube, KubeGroup, service, names, execStart)
updateMaps := kube.LookupAllStrv(KubeGroup, KeyAutoUpdate)
for _, update := range updateMaps {
@ -1082,14 +1098,17 @@ func handleUserNS(unitFile *parser.UnitFile, groupName string, podman *PodmanCmd
}
}
func addNetworks(quadletUnitFile *parser.UnitFile, groupName string, serviceUnitFile *parser.UnitFile, podman *PodmanCmdline) {
func addNetworks(quadletUnitFile *parser.UnitFile, groupName string, serviceUnitFile *parser.UnitFile, names map[string]string, podman *PodmanCmdline) {
networks := quadletUnitFile.LookupAll(groupName, KeyNetwork)
for _, network := range networks {
if len(network) > 0 {
quadletNetworkName, options, found := strings.Cut(network, ":")
if strings.HasSuffix(quadletNetworkName, ".network") {
// the podman network name is systemd-$name
networkName := replaceExtension(quadletNetworkName, "", "systemd-", "")
// the podman network name is systemd-$name if none is specified by the user.
networkName := names[quadletNetworkName]
if networkName == "" {
networkName = replaceExtension(quadletNetworkName, "", "systemd-", "")
}
// the systemd unit name is $name-network.service
networkServiceName := replaceExtension(quadletNetworkName, ".service", "", "-network")
@ -1209,7 +1228,7 @@ func handleLogDriver(unitFile *parser.UnitFile, groupName string, podman *Podman
}
}
func handleStorageSource(quadletUnitFile, serviceUnitFile *parser.UnitFile, source string) (string, error) {
func handleStorageSource(quadletUnitFile, serviceUnitFile *parser.UnitFile, source string, names map[string]string) (string, error) {
if source[0] == '.' {
var err error
source, err = getAbsolutePath(quadletUnitFile, source)
@ -1221,8 +1240,11 @@ func handleStorageSource(quadletUnitFile, serviceUnitFile *parser.UnitFile, sour
// Absolute path
serviceUnitFile.Add(UnitGroup, "RequiresMountsFor", source)
} else if strings.HasSuffix(source, ".volume") {
// the podman volume name is systemd-$name
volumeName := replaceExtension(source, "", "systemd-", "")
// the podman volume name is systemd-$name if none has been provided by the user.
volumeName := names[source]
if volumeName == "" {
volumeName = replaceExtension(source, "", "systemd-", "")
}
// the systemd unit name is $name-volume.service
volumeServiceName := replaceExtension(source, ".service", "", "-volume")

View File

@ -0,0 +1,4 @@
## assert-podman-final-args "test-network"
[Network]
NetworkName=test-network

View File

@ -0,0 +1,4 @@
## assert-podman-final-args "test-volume"
[Volume]
VolumeName=test-volume

View File

@ -606,6 +606,7 @@ BOGUS=foo
Entry("device-copy.volume", "device-copy.volume", 0, ""),
Entry("device.volume", "device.volume", 0, ""),
Entry("label.volume", "label.volume", 0, ""),
Entry("name.volume", "name.volume", 0, ""),
Entry("podmanargs.volume", "podmanargs.volume", 0, ""),
Entry("uid.volume", "uid.volume", 0, ""),
@ -635,6 +636,7 @@ BOGUS=foo
Entry("Network - Internal network", "internal.network", 0, ""),
Entry("Network - Label", "label.network", 0, ""),
Entry("Network - Multiple Options", "options.multiple.network", 0, ""),
Entry("Network - Name", "name.network", 0, ""),
Entry("Network - Options", "options.network", 0, ""),
Entry("Network - PodmanArgs", "podmanargs.network", 0, ""),
Entry("Network - Range not enough Subnet", "range.less-subnet.network", 1, "converting \"range.less-subnet.network\": cannot set more ranges than subnets"),

View File

@ -47,8 +47,11 @@ function run_quadlet() {
local service=$(quadlet_to_service_name "$sourcefile")
# quadlet always works on an entire directory, so copy the file
# to transform to a tmpdir
local quadlet_tmpdir=$(mktemp -d --tmpdir=$PODMAN_TMPDIR quadlet.XXXXXX)
# to transform to the given or newly created tmpdir
local quadlet_tmpdir="$2"
if [ -z "$quadlet_tmpdir" ]; then
quadlet_tmpdir=$(mktemp -d --tmpdir=$PODMAN_TMPDIR quadlet.XXXXXX)
fi
cp $sourcefile $quadlet_tmpdir/
echo "$_LOG_PROMPT $QUADLET $_DASHUSER $UNIT_DIR"
@ -337,6 +340,61 @@ EOF
run_podman volume rm $volume_name
}
# A quadlet container depends on a named quadlet volume
@test "quadlet - named volume dependency" {
# Save the unit name to use as the volume for the container
local quadlet_vol_unit=dep_$(random_string).volume
local quadlet_vol_file=$PODMAN_TMPDIR/${quadlet_vol_unit}
cat > $quadlet_vol_file <<EOF
[Volume]
VolumeName=foo
EOF
# Have quadlet create the systemd unit file for the volume unit
local quadlet_tmpdir=$(mktemp -d --tmpdir=$PODMAN_TMPDIR quadlet.XXXXXX)
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="foo"
local quadlet_file=$PODMAN_TMPDIR/user_$(random_string).container
cat > $quadlet_file <<EOF
[Container]
Image=$IMAGE
Exec=top
Volume=$quadlet_vol_unit:/tmp
EOF
# Have quadlet create the systemd unit file for the container unit
run_quadlet "$quadlet_file" "$quadlet_tmpdir"
# Save the container service name for readability
local container_service=$QUADLET_SERVICE_NAME
local container_name=systemd-$(basename $quadlet_file .container)
# Volume should not exist
run_podman 1 volume exists ${volume_name}
# Start the container service which should also trigger the start of the volume service
service_setup $container_service
# Volume system unit should be active
run systemctl show --property=ActiveState "$vol_service"
assert "$output" = "ActiveState=active" "volume should be active via dependency"
# Volume should exist
run_podman volume exists ${volume_name}
# Container should be attached to defined volume
run_podman container inspect --format "{{(index .Mounts 0).Name}}" $container_name
assert "$output" = "$volume_name" "container should be attached to network $volume_name"
# Shutdown the service and remove the volume
service_cleanup $container_service failed
run_podman volume rm $volume_name
}
@test "quadlet - network" {
local quadlet_file=$PODMAN_TMPDIR/basic_$(random_string).network
cat > $quadlet_file <<EOF
@ -405,6 +463,58 @@ EOF
run_podman network rm $network_name
}
# A quadlet container depends on a named quadlet network
@test "quadlet - named network dependency" {
# Save the unit name to use as the network for the container
local quadlet_network_unit=dep_$(random_string).network
local quadlet_network_file=$PODMAN_TMPDIR/${quadlet_network_unit}
cat > $quadlet_network_file <<EOF
[Network]
NetworkName=foo
EOF
# Have quadlet create the systemd unit file for the network unit
local quadlet_tmpdir=$(mktemp -d --tmpdir=$PODMAN_TMPDIR quadlet.XXXXXX)
run_quadlet "$quadlet_network_file" "$quadlet_tmpdir"
# Save the network service name since the variable will be overwritten
local network_service=$QUADLET_SERVICE_NAME
local network_name="foo"
local quadlet_file=$PODMAN_TMPDIR/user_$(random_string).container
cat > $quadlet_file <<EOF
[Container]
Image=$IMAGE
Exec=top
Network=$quadlet_network_unit
EOF
run_quadlet "$quadlet_file" "$quadlet_tmpdir"
# Save the container service name for readability
local container_service=$QUADLET_SERVICE_NAME
local container_name=systemd-$(basename $quadlet_file .container)
# Network should not exist
run_podman 1 network exists $network_name
service_setup $container_service
# Network system unit should be active
run systemctl show --property=ActiveState "$network_service"
assert "$output" = "ActiveState=active" "network should be active via dependency"
# Network should exist
run_podman network exists $network_name
# Container should be attached to defined network
run_podman container inspect --format "{{index .NetworkSettings.Networks \"$network_name\"}}" $container_name
assert "$output" != "<nil>" "container should be attached to network $network_name"
service_cleanup $QUADLET_SERVICE_NAME failed
run_podman network rm $network_name
}
@test "quadlet kube - basic" {
# Create the YAMl file
yaml_source="$PODMAN_TMPDIR/basic_$(random_string).yaml"
@ -444,6 +554,77 @@ EOF
run_podman rmi $(pause_image)
}
@test "quadlet kube - named network dependency" {
# Save the unit name to use as the network for the container
local quadlet_network_unit=dep_$(random_string).network
local quadlet_network_file=$PODMAN_TMPDIR/${quadlet_network_unit}
cat > $quadlet_network_file <<EOF
[Network]
NetworkName=foo
EOF
# Have quadlet create the systemd unit file for the network unit
local quadlet_tmpdir=$(mktemp -d --tmpdir=$PODMAN_TMPDIR quadlet.XXXXXX)
run_quadlet "$quadlet_network_file" "$quadlet_tmpdir"
# Save the network service name since the variable will be overwritten
local network_service=$QUADLET_SERVICE_NAME
local network_name="foo"
# Create the YAMl file
yaml_source="$PODMAN_TMPDIR/basic_$(random_string).yaml"
cat >$yaml_source <<EOF
apiVersion: v1
kind: Pod
metadata:
labels:
app: test
name: test_pod
spec:
containers:
- command:
- top
image: $IMAGE
name: test
EOF
# Create the Quadlet file
local quadlet_file=$PODMAN_TMPDIR/basic_$(random_string).kube
cat > $quadlet_file <<EOF
[Kube]
Yaml=${yaml_source}
Network=$quadlet_network_unit
EOF
# Network should not exist
run_podman 1 network exists $network_name
run_quadlet "$quadlet_file" "$quadlet_tmpdir"
service_setup $QUADLET_SERVICE_NAME
# Network system unit should be active
run systemctl show --property=ActiveState "$network_service"
assert "$output" = "ActiveState=active" "network should be active via dependency"
# Network should exist
run_podman network exists $network_name
# Ensure we have output. Output is synced via sd-notify (socat in Exec)
run journalctl "--since=$STARTED_TIME" --unit="$QUADLET_SERVICE_NAME"
assert "$output" =~ '.*Started.*\.service.*'
run_podman container inspect --format "{{.State.Status}}" test_pod-test
assert "$output" =~ "running" "container should be started by systemd and hence be running"
# Container should be attached to defined network
run_podman container inspect --format "{{index .NetworkSettings.Networks \"$network_name\"}}" test_pod-test
assert "$output" != "<nil>" "container should be attached to network $network_name"
service_cleanup $QUADLET_SERVICE_NAME inactive
run_podman network rm $network_name
run_podman rmi $(pause_image)
}
@test "quadlet - rootfs" {
skip_if_no_selinux
skip_if_rootless