From 3825d4a871e52ebfb9f82459f7f672f3107a3271 Mon Sep 17 00:00:00 2001 From: Simon de Vlieger Date: Tue, 27 Jun 2023 14:42:09 +0200 Subject: [PATCH] dnf.mark: mark packages in DNF state database This adjustment allows the definition of the mark with the RPMs and runs DNF after installing the RPMs to put the proper markings in the DNF state database. See #455. This ensures that packages don't get removed during `autoremove` leading to broken systems. This stage conditionally selects `dnf5` or `dnf` semantics. --- stages/org.osbuild.dnf.mark | 105 ++++++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100755 stages/org.osbuild.dnf.mark diff --git a/stages/org.osbuild.dnf.mark b/stages/org.osbuild.dnf.mark new file mode 100755 index 0000000000..6072f1d43f --- /dev/null +++ b/stages/org.osbuild.dnf.mark @@ -0,0 +1,105 @@ +#!/usr/bin/python3 +""" +Mark packages in the DNF state database. +""" + +import shutil +import sys +import subprocess + +from osbuild import api + +SCHEMA_2 = """ +"options": { + "additionalProperties": false, + "properties": { + "packages": { + "type": "array", + "minItems": 1, + "description": "Packages and their marks.", + "items": { + "type": "object", + "additionalProperties": false, + "required": ["name", "mark"], + "properties": { + "name": { + "type": "string", + "description": "Package name." + }, + "mark": { + "type": "string", + "enum": ["user", "dependency", "weak", "group"], + "description": "Package mark." + }, + "group": { + "type": "string", + "description": "Group to mark package for when `mark` is `group`." + } + } + } + } + } +} +""" + + +def mark(packages): + dnf_bin = shutil.which("dnf5") + + # let's start by finding out which version of dnf we have + version = subprocess.run( + [dnf_bin, "--version"], capture_output=True, encoding="utf8", check=True + ).stdout + + # now it's important to note that dnf and dnf5 have different version outputs, + # dnf outputs it's version directly and dnf5 has a prefix. we can strip that + # prefix and it'll be a no-op if it isn't there, both have this on the first + # line + version = version.splitlines()[0] + version = version.removeprefix("dnf5 version ") + version, _ = version.split(".", 1) + + # if that's a 5, then we have dnf5 + is_dnf5 = version == "5" + + # now there is an alternative and that is dnf5 installed separately, in which + # case we adjust our path and turn on dnf5 + dnf5_bin = shutil.which("dnf5") + + if dnf5_bin: + dnf_bin = dnf5_bin + is_dnf5 = True + + markings = {} + + for package in packages: + if package["mark"] not in markings: + markings[package["mark"]] = [] + markings[package["mark"]].append(package) + + # we can mark more things with dnf5 + if is_dnf5: + for mark_as in ["user", "dependency", "weak"]: + subprocess.run( + [dnf_bin, "mark", mark_as] + + [package["name"] for package in markings[mark]], + check=True, + ) + else: + for mark_as in ["user"]: + subprocess.run( + [dnf_bin, "mark", mark_as] + + [package["name"] for package in markings[mark]], + check=True, + ) + + +def main(_, options): + mark(options["packages"]) + return 0 + + +if __name__ == "__main__": + args = api.arguments() + r = main(args["tree"], args["options"]) + sys.exit(r)