stages: extend org.osbuild.systemd to create .service unit drop-ins

Extend the `org.osbuild.systemd` stage to create drop-in configuration
files for Systemd `.service` units under `/usr/lib/systemd/system`.
Currently only the `Environment` option in the `Service` section can be
configured.

Update the `org.osbuild.systemd` stage test case to create drop-in
configuration `10-rh-enable-for-ec2.conf` for `nm-cloud-setup.service`
unit, as used in RHEL AMI images.

Signed-off-by: Tomas Hozza <thozza@redhat.com>
This commit is contained in:
Tomas Hozza 2021-06-09 18:12:56 +02:00 committed by Tomas Hozza
parent 7765c53ecc
commit 1fef6e76fb
4 changed files with 100 additions and 8 deletions

View file

@ -1,25 +1,38 @@
#!/usr/bin/python3
"""
Enable or disable systemd services
Configure Systemd services.
Enable or disable systemd units (service, socket, path, etc.)
Enable, disable or mask systemd units (service, socket, path, etc.) by running
`systemctl` from the buildhost.
This stage runs `systemctl enable` for all `enabled_services` items, which may
create symlinks under `/etc/systemd/system`.
After enabling units, it runs `systemctl disable` for all `disabled_services`
items, which will delete _all_ symlinks to the named services.
Uses `systemctl` from the buildhost.
The 'default_target' option allows to configure the default Systemd target.
The 'unit_dropins' option allows to create Systemd unit drop-in configuration
files in `/usr/lib/systemd/system/<unit_name>.d/`. Its properties are names of
'.service' files to be modified using drop-ins. These names are validated using
the same rules as specified by systemd.unit(5) and they must contain the
'.service' suffix (other types of unit files are not supported). Value of each
specified '.service' file is an object, which properties are names of drop-in
configuration '.conf' files. Drop-in configuration files can currently specify
the following subset of options:
- 'Service' section
- 'Environment' option
"""
import os
import subprocess
import sys
import configparser
import osbuild.api
SCHEMA = """
SCHEMA = r"""
"additionalProperties": false,
"properties": {
"enabled_services": {
@ -40,16 +53,73 @@ SCHEMA = """
"default_target": {
"type": "string",
"description": "The default target to boot into"
},
"unit_dropins": {
"additionalProperties": false,
"type": "object",
"description": "Systemd unit drop-in configurations.",
"patternProperties": {
"^[\\w:.\\\\-]+[@]{0,1}[\\w:.\\\\-]*\\.service$": {
"additionalProperties": false,
"type": "object",
"description": "Drop-in configurations for a '.service' unit.",
"patternProperties": {
"^[\\w.-]{1,250}\\.conf$": {
"additionalProperties": false,
"type": "object",
"description": "Drop-in configuration for a '.service' unit.",
"properties": {
"Service": {
"additionalProperties": false,
"type": "object",
"description": "'Service' configuration section of a unit file.",
"properties": {
"Environment": {
"type": "string",
"description": "Sets environment variables for executed process."
}
}
}
}
}
}
}
}
}
}
"""
def configure_unit_dropins(tree, unit_dropins_options):
for unit, unit_dropins in unit_dropins_options.items():
# ensure the unit name + ".d" does not exceed maximum filename length
if len(unit+".d") > 255:
raise ValueError(f"Error: the {unit} unit drop-in directory exceeds the maximum filename length.")
unit_dropins_dir = f"{tree}/usr/lib/systemd/system/{unit}.d"
os.makedirs(unit_dropins_dir, exist_ok=True)
for dropin_file, dropin_config in unit_dropins.items():
config = configparser.ConfigParser()
# prevent conversion of the option name to lowercase
config.optionxform = lambda option: option
for section, options in dropin_config.items():
if not config.has_section(section):
config.add_section(section)
for option, value in options.items():
config.set(section, option, str(value))
with open(f"{unit_dropins_dir}/{dropin_file}", "w") as f:
config.write(f, space_around_delimiters=False)
def main(tree, options):
enabled_services = options.get("enabled_services", [])
disabled_services = options.get("disabled_services", [])
masked_services = options.get("masked_services", [])
default_target = options.get("default_target")
unit_dropins_options = options.get("unit_dropins", {})
for service in enabled_services:
subprocess.run(["systemctl", "--root", tree, "enable", service], check=True)
@ -63,6 +133,8 @@ def main(tree, options):
if default_target:
subprocess.run(["systemctl", "--root", tree, "set-default", default_target], check=True)
configure_unit_dropins(tree, unit_dropins_options)
return 0

View file

@ -456,7 +456,16 @@
],
"disabled_services": [
"sshd"
]
],
"unit_dropins": {
"nm-cloud-setup.service": {
"10-rh-enable-for-ec2.conf": {
"Service": {
"Environment": "NM_CLOUD_SETUP_EC2=yes"
}
}
}
}
}
}
]

View file

@ -42,7 +42,16 @@
],
"disabled_services": [
"sshd"
]
],
"unit_dropins": {
"nm-cloud-setup.service": {
"10-rh-enable-for-ec2.conf": {
"Service": {
"Environment": "NM_CLOUD_SETUP_EC2=yes"
}
}
}
}
}
}
]

View file

@ -1,7 +1,9 @@
{
"added_files": [
"/etc/systemd/system/ldconfig.service",
"/etc/systemd/system/multi-user.target.wants/nftables.service"
"/etc/systemd/system/multi-user.target.wants/nftables.service",
"/usr/lib/systemd/system/nm-cloud-setup.service.d",
"/usr/lib/systemd/system/nm-cloud-setup.service.d/10-rh-enable-for-ec2.conf"
],
"deleted_files": [
"/etc/systemd/system/multi-user.target.wants/sshd.service"