Add org.osbuild.containers.unit.create stage

This is essentially org.osbuild.systemd.unit.create but creates
file where podman looks for quadlets instead.

For now only container, volume and network is supported.

Not all quadlet options are supported, but at least the most usef ones, and enough for the automotive sample-images.
This commit is contained in:
Alexander Larsson 2024-09-17 13:54:54 +02:00 committed by Simon de Vlieger
parent af3c70fb40
commit 9f0f609d31
2 changed files with 391 additions and 0 deletions

View file

@ -0,0 +1,61 @@
#!/usr/bin/python3
import configparser
import sys
import osbuild.api
def validate(filename, cfg):
# ensure the service name does not exceed maximum filename length
if len(filename) > 255:
raise ValueError(f"Error: the {filename} unit exceeds the maximum filename length.")
# Filename extension must match the config:
# .service requires a Service section
# .mount requires a Mount section
if filename.endswith(".container") and "Container" not in cfg:
raise ValueError(f"Error: {filename} unit requires Container section")
if filename.endswith(".volume") and "Volume" not in cfg:
raise ValueError(f"Error: {filename} unit requires Volume section")
if filename.endswith(".network") and "Network" not in cfg:
raise ValueError(f"Error: {filename} unit requires Network section")
def main(tree, options):
filename = options["filename"]
cfg = options["config"]
validate(filename, cfg)
# We trick configparser into letting us write multiple instances of the same option by writing them as keys with no
# value, so we enable allow_no_value
config = configparser.ConfigParser(allow_no_value=True, interpolation=None)
# prevent conversion of the option name to lowercase
config.optionxform = lambda option: option
for section, opts in cfg.items():
if not config.has_section(section):
config.add_section(section)
for option, value in opts.items():
if isinstance(value, list):
for v in value:
if option == "Environment":
# Option value becomes "KEY=VALUE" (quoted)
v = '"' + v["key"] + "=" + str(v["value"]) + '"'
config.set(section, str(option) + "=" + str(v))
else:
config.set(section, option, str(value))
persistent = options.get("unit-path", "usr")
systemd_dir = str()
if persistent == "usr":
systemd_dir = f"{tree}/usr/share/containers/systemd"
elif persistent == "etc":
systemd_dir = f"{tree}/etc/containers/systemd"
with open(f"{systemd_dir}/{filename}", "w", encoding="utf8") as f:
config.write(f, space_around_delimiters=False)
if __name__ == '__main__':
args = osbuild.api.arguments()
r = main(args["tree"], args["options"])
sys.exit(r)

View file

@ -0,0 +1,330 @@
{
"summary": "Create a podman systemd unit file",
"description": [
"This stage allows to create Podman systemd (quadlet) unit files. The `filename` property",
"specifies the, '.service' or '.mount' file to be added. These names are",
"validated using the, same rules as specified by podman-systemd.unit(5) and they",
"must contain the, '.container', '.volume' or '.network' suffix (other types of unit files",
"are not supported). 'unit-path' determines determine the unit load path.",
"",
"The Unit configuration can currently specify the following subset",
"of options:",
" - 'Unit' section",
" - 'Description' - string",
" - 'ConditionPathExists' - string",
" - 'ConditionPathIsDirectory' - string",
" - 'DefaultDependencies' - bool",
" - 'Requires' - [strings]",
" - 'Wants' - [strings]",
" - 'After' - [strings]",
" - 'Before' - [strings]",
" - 'Service' section",
" - 'Restart' - string",
" - 'Container' section",
" - 'Image' - string",
" - 'Exec' - string",
" - 'Volume' - [string]",
" - 'User' - string",
" - 'Group' - string",
" - 'AddDevice' - string",
" - 'Environment' - [object]",
" - 'Network' - string",
" - 'WorkingDir' - string",
" - 'Volume' section",
" - 'VolumeName' - string",
" - 'Driver' - string",
" - 'Image' - string",
" - 'User' - string",
" - 'Group' - string",
" - 'Network' section",
" - 'Gateway' - string",
" - 'DNS' - string",
" - 'IPRange' - string",
" - 'Subnet' - string",
" - 'Driver' - string",
" - 'NetworkName' - string",
" - 'Install' section",
" - 'WantedBy' - [string]",
" - 'RequiredBy' - [string]"
],
"schema": {
"additionalProperties": false,
"required": [
"filename",
"config"
],
"properties": {
"filename": {
"type": "string",
"pattern": "^[\\w:.\\\\-]+[@]{0,1}[\\w:.\\\\-]*\\.(container|volume|network)$"
},
"unit-path": {
"type": "string",
"enum": [
"usr",
"etc"
],
"default": "usr",
"description": "Define the system load path"
},
"config": {
"additionalProperties": false,
"type": "object",
"oneOf": [
{
"required": [
"Unit",
"Container",
"Install"
],
"not": {
"required": [
"Service",
"Volume",
"Network"
]
}
},
{
"required": [
"Volume"
],
"not": {
"required": [
"Container",
"Network",
"Service"
]
}
},
{
"required": [
"Network"
],
"not": {
"required": [
"Service",
"Container",
"Volume"
]
}
}
],
"description": "Configuration for a '.container' unit.",
"properties": {
"Unit": {
"additionalProperties": false,
"type": "object",
"description": "'Unit' configuration section of a unit file.",
"properties": {
"Description": {
"type": "string"
},
"Wants": {
"type": "array",
"items": {
"type": "string"
}
},
"After": {
"type": "array",
"items": {
"type": "string"
}
},
"Before": {
"type": "array",
"items": {
"type": "string"
}
},
"Requires": {
"type": "array",
"items": {
"type": "string"
}
},
"ConditionPathExists": {
"type": "array",
"items": {
"type": "string"
}
},
"ConditionPathIsDirectory": {
"type": "array",
"items": {
"type": "string"
}
},
"DefaultDependencies": {
"type": "boolean"
}
}
},
"Service": {
"additionalProperties": false,
"type": "object",
"description": "'Service' configuration section of a unit file.",
"properties": {
"Restart": {
"type": "string",
"enum": [
"no",
"on-success",
"on-failure",
"on-abnormal",
"on-watchdog",
"on-abort",
"always"
]
}
}
},
"Container": {
"additionalProperties": false,
"type": "object",
"description": "'Container' configuration section of a unit file.",
"required": [
"Image"
],
"properties": {
"Environment": {
"type": "array",
"description": "Sets environment variables for executed process.",
"items": {
"type": "object",
"description": "Sets environment variables for executed process.",
"additionalProperties": false,
"properties": {
"key": {
"type": "string",
"pattern": "^[A-Za-z_][A-Za-z0-9_]*"
},
"value": {
"type": "string"
}
}
}
},
"Image": {
"description": "Container Image to use",
"type": "string"
},
"Exec": {
"description": "Command to execute in container",
"type": "string"
},
"Volume": {
"description": "Volumes to use",
"type": "array",
"items": {
"type": "string"
}
},
"User": {
"description": "Run as user",
"type": "string"
},
"Group": {
"description": "Run as group",
"type": "string"
},
"AddDevice": {
"description": "Add device to container",
"type": "string"
},
"Network": {
"description": "What network option to use",
"type": "string"
},
"WorkingDir": {
"description": "Working directory for initial process",
"type": "string"
}
}
},
"Volume": {
"additionalProperties": false,
"type": "object",
"description": "'Volume' configuration section of a unit file.",
"required": [
"What"
],
"properties": {
"VolumeName": {
"description": "Override volume name",
"type": "string"
},
"Driver": {
"description": "What volume driver to use",
"type": "string"
},
"Image": {
"description": "Image to use if driver is image",
"type": "string"
},
"User": {
"description": "User to use as owner of the volume",
"type": "string"
},
"Group": {
"description": "Group to use as owner of the volume",
"type": "string"
}
}
},
"Network": {
"additionalProperties": false,
"type": "object",
"description": "'Network' configuration section of a unit file.",
"properties": {
"Gateway": {
"description": "Addres of gaterway",
"type": "boolean"
},
"DNS": {
"description": "Address of DNS server",
"type": "boolean"
},
"IPRange": {
"description": "Range to allocate IPs from",
"type": "boolean"
},
"Subnet": {
"description": "Subnet in CIDR notation",
"type": "boolean"
},
"Driver": {
"description": "What network driver to use",
"type": "boolean"
},
"NetworkName": {
"description": "Override network name",
"type": "boolean"
}
}
},
"Install": {
"additionalProperties": false,
"type": "object",
"description": "'Install' configuration section of a unit file.",
"properties": {
"WantedBy": {
"type": "array",
"items": {
"type": "string"
}
},
"RequiredBy": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
}
}
}
}