stages: add org.osbuild.nm.conf

New stage to create NetworkManager configuration files. Currently
only a subset of settings are supported. Include a simple stage
test case for it.
This commit is contained in:
Christian Kellner 2021-07-27 22:07:12 +00:00 committed by Tom Gundersen
parent 152b14a0d6
commit 6f7382b885
6 changed files with 1388 additions and 0 deletions

204
stages/org.osbuild.nm.conf Executable file
View file

@ -0,0 +1,204 @@
#!/usr/bin/python3
"""
Create NetworkManager configuration files.
This stages allows to create NetworkManager configuration files in the
location specified via `path` with the `settings` provided. Only a
subset of the all options are currently supported.
The stage will use `true` and `false` for boolean values and `,` as
list separator.
NetworkManger looks in the following locations for configuration files:
- /etc/NetworkManager/NetworkManager.conf
- /etc/NetworkManager/conf.d/name.conf
- /usr/lib/NetworkManager/conf.d/name.conf,
and also in the following locations that very likely should not be used:
- /run/NetworkManager/conf.d/name.conf
-/var/lib/NetworkManager/NetworkManager-intern.conf
"""
import configparser
import os
import sys
import osbuild.api
SCHEMA = r"""
"definitions": {
"device-list": {
"type": "array",
"minItems": 1,
"items": {
"type": "string"
}
},
"str-array": {
"type": "array",
"minItems": 1,
"items": {
"type": "string"
}
},
"device": {
"type": "object",
"additionalProperties": false,
"properties": {
"managed": {
"type": "boolean"
},
"wifi.scan-rand-mac-address": {
"type": "boolean"
}
}
},
"global-dns-domain": {
"type": "object",
"additionalProperties": false,
"required": ["servers"],
"properties": {
"servers": {
"type": "array",
"minItems": 1,
"items": {
"type": "string"
}
}
}
}
},
"additionalProperties": false,
"required": ["path", "settings"],
"properties": {
"path": {
"type": "string"
},
"settings": {
"type": "object",
"additionalProperties": false,
"properties": {
"main": {
"type": "object",
"additionalProperties": false,
"properties": {
"no-auto-default": {
"$ref": "#/definitions/device-list"
},
"plugins": {
"$ref": "#/definitions/str-array"
}
}
},
"device": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"additionalProperties": false,
"required": ["config"],
"properties": {
"name": {
"type": "string"
},
"config": {
"$ref": "#/definitions/device"
}
}
}
},
"global-dns-domain": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"additionalProperties": false,
"required": ["name", "config"],
"properties": {
"name": {
"type": "string"
},
"config": {
"$ref": "#/definitions/global-dns-domain"
}
}
}
},
"keyfile": {
"type": "object",
"additionalProperties": false,
"properties": {
"unmanaged-devices": {
"$ref": "#/definitions/device-list"
}
}
}
}
}
}
"""
def make_value(value):
if isinstance(value, list):
val = ",".join(value)
elif isinstance(value, bool):
val = "true" if value else "false"
else:
val = str(value)
return val
def make_section(cfg, name, settings):
if not cfg.has_section(name):
cfg.add_section(name)
for key, value in settings.items():
val = make_value(value)
cfg.set(name, key, val)
def make_named_section(cfg, name: str, section):
config = section["config"]
suffix = section.get("name")
if suffix:
name = f"{name}-{suffix}"
make_section(cfg, name, config)
def main(tree, options):
path = options["path"]
settings = options["settings"]
cfgfile = os.path.join(tree, path.lstrip("/"))
cfgpath = os.path.dirname(cfgfile)
os.makedirs(cfgpath, exist_ok=True)
cfg = configparser.ConfigParser()
cfg.optionxform = lambda o: o
for name, items in settings.items():
if isinstance(items, dict):
make_section(cfg, name, items)
elif isinstance(items, list):
for item in items:
make_named_section(cfg, name, item)
else:
raise ValueError(f"Invalid section type: {type(items)}")
with open(cfgfile, "w") as f:
os.fchmod(f.fileno(), 0o600)
cfg.write(f, space_around_delimiters=False)
return 0
if __name__ == '__main__':
args = osbuild.api.arguments()
r = main(args["tree"], args["options"])
sys.exit(r)