diff --git a/stages/org.osbuild.dnf.module-config b/stages/org.osbuild.dnf.module-config new file mode 100755 index 00000000..2ae4479b --- /dev/null +++ b/stages/org.osbuild.dnf.module-config @@ -0,0 +1,29 @@ +#!/usr/bin/python3 +import configparser +import os +import sys + +import osbuild.api + + +def main(tree, options): + path = options["path"] + conf = options["conf"] + + inip = configparser.ConfigParser() + inip[conf["name"]] = conf + + # we need to handle enabled profiles as a string as configparser + # would normally write [a, b] + inip[conf["name"]]["profiles"] = ", ".join(conf["profiles"]) + + with open(os.path.join(tree, path), "w", encoding="utf-8") as file: + inip.write(file) + + return 0 + + +if __name__ == '__main__': + args = osbuild.api.arguments() + r = main(args["tree"], args["options"]) + sys.exit(r) diff --git a/stages/org.osbuild.dnf.module-config.meta.json b/stages/org.osbuild.dnf.module-config.meta.json new file mode 100644 index 00000000..b59c24e6 --- /dev/null +++ b/stages/org.osbuild.dnf.module-config.meta.json @@ -0,0 +1,41 @@ +{ + "summary": "Write DNF module configuration.", + "description": [ + "This stage allows writing DNF module configurations. These files are", + "which are used by DNF to determine enabled modules." + ], + "schema_2": { + "options": { + "additionalProperties": false, + "description": "DNF configuration.", + "properties": { + "path": { + "type": "string", + "description": "Path to write the module configuration to." + }, + "conf": { + "additionalProperties": false, + "type": "object", + "description": "DNF module configuration values.", + "properties": { + "name": { + "type": "string" + }, + "stream": { + "type": "string" + }, + "state": { + "type": "string" + }, + "profiles": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + } + } +} diff --git a/stages/test/test_dnf_module_config.py b/stages/test/test_dnf_module_config.py new file mode 100644 index 00000000..2e2f66cb --- /dev/null +++ b/stages/test/test_dnf_module_config.py @@ -0,0 +1,71 @@ +#!/usr/bin/python3 + +import pytest + +from osbuild import testutil + +STAGE_NAME = "org.osbuild.dnf.module-config" + + +@pytest.mark.parametrize("test_data,expected_err", [ + # bad + ({"conf": "must-be-object"}, "'must-be-object' is not of type 'object'"), + ({"path": {}}, "{} is not of type 'string'"), + # good + ({ + "conf": + { + "name": "some-module", + "stream": "some-stream", + "state": "some-state", + "profiles": ["some-profile"], + }, + }, "") +]) +def test_dnf_module_config_schema_validation(stage_schema, test_data, expected_err): + test_input = { + "type": STAGE_NAME, + "options": { + } + } + test_input["options"].update(test_data) + res = stage_schema.validate(test_input) + + if expected_err == "": + assert res.valid is True, f"err: {[e.as_dict() for e in res.errors]}" + else: + assert res.valid is False + testutil.assert_jsonschema_error_contains(res, expected_err, expected_num_errs=1) + + +def test_dnf_module_config_writes_file(tmp_path, stage_module): + treepath = tmp_path / "tree" + confpath = "etc/dnf/modules.d/module.conf" + fullpath = treepath / confpath + + fullpath.parent.mkdir(parents=True, exist_ok=True) + + options = { + "path": confpath, + "conf": { + "name": "some-module", + "stream": "some-stream", + "state": "some-state", + "profiles": ["some-profile", "other-profile"], + } + } + + stage_module.main(treepath, options) + + assert fullpath.exists() + + confdata = fullpath.read_text() + + assert confdata == """\ +[some-module] +name = some-module +stream = some-stream +state = some-state +profiles = some-profile, other-profile + +"""