Repository: osbuild/osbuild
Pull request #1894 (2024-09-19 18:38:55)

spec: use python3-libdnf5 in RHEL 11 and ELN

supakeen: Will conflict with #1893 but only due to the comment.
yselkowitz: > Can you also please amend the condition on the line 230, so dnf5 actually is used on RHEL 11+?

Rebased and done.  One question though, what purpose does the top-level `Requires: dnf` serve?  On Fedora 41+ that is provided by dnf5, should that also be conditional, or is that a dependency on the "dnf" command line?
yselkowitz: ping @ondrejbudai @supakeen 

Patch:
diff --git a/osbuild.spec b/osbuild.spec
index 87bd4d59..aab59034 100644
--- a/osbuild.spec
+++ b/osbuild.spec
@@ -1,7 +1,7 @@
 %global         forgeurl https://github.com/osbuild/osbuild
 %global         selinuxtype targeted
 
-Version:        130
+Version:        131
 
 %forgemeta
 
@@ -137,8 +137,8 @@ manifests and osbuild.
 Summary:        Dependency solving support for DNF
 Requires:       %{name} = %{version}-%{release}
 
-# Fedora 41 and later use libdnf5, RHEL and Fedora < 41 use dnf
-%if 0%{?fedora} >= 41
+# RHEL 11 and Fedora 41 and later use libdnf5, RHEL < 11 and Fedora < 41 use dnf
+%if 0%{?fedora} >= 41 || 0%{?rhel} >= 11
 Requires: python3-libdnf5 >= 5.2.1
 %else
 Requires: python3-dnf
@@ -226,8 +226,8 @@ install -p -m 0755 tools/osbuild-depsolve-dnf %{buildroot}%{_libexecdir}/osbuild
 
 # Configure the solver for dnf
 mkdir -p %{buildroot}%{_datadir}/osbuild
-# Fedora 41 and later use dnf5, RHEL and Fedora < 41 use dnf
-%if 0%{?fedora} >= 41
+# RHEL 11 and Fedora 41 and later use dnf5, RHEL < 11 and Fedora < 41 use dnf
+%if 0%{?fedora} >= 41 || 0%{?rhel} >= 11
 install -p -m 0644 tools/solver-dnf5.json %{buildroot}%{pkgdir}/solver.json
 %else
 install -p -m 0644 tools/solver-dnf.json %{buildroot}%{pkgdir}/solver.json
diff --git a/osbuild/__init__.py b/osbuild/__init__.py
index dbe8cab6..e2a1f92a 100644
--- a/osbuild/__init__.py
+++ b/osbuild/__init__.py
@@ -10,7 +10,7 @@ independent of osbuild but used across the osbuild codebase.
 
 from .pipeline import Manifest, Pipeline, Stage
 
-__version__ = "130"
+__version__ = "131"
 
 __all__ = [
     "Manifest",
diff --git a/setup.py b/setup.py
index c407bf8e..03ae1b06 100644
--- a/setup.py
+++ b/setup.py
@@ -2,7 +2,7 @@ import setuptools
 
 setuptools.setup(
     name="osbuild",
-    version="130",
+    version="131",
     description="A build system for OS images",
     packages=[
         "osbuild",
diff --git a/stages/org.osbuild.selinux b/stages/org.osbuild.selinux
index bb45298d..563d827b 100755
--- a/stages/org.osbuild.selinux
+++ b/stages/org.osbuild.selinux
@@ -8,11 +8,14 @@ from osbuild.util import selinux
 
 
 def main(tree, options):
-    file_contexts = os.path.join(f"{tree}", options["file_contexts"])
+    file_contexts = options.get("file_contexts")
     exclude_paths = options.get("exclude_paths")
-    if exclude_paths:
-        exclude_paths = [os.path.join(tree, p.lstrip("/")) for p in exclude_paths]
-    selinux.setfiles(file_contexts, os.fspath(tree), "", exclude_paths=exclude_paths)
+
+    if file_contexts:
+        file_contexts = os.path.join(f"{tree}", options["file_contexts"])
+        if exclude_paths:
+            exclude_paths = [os.path.join(tree, p.lstrip("/")) for p in exclude_paths]
+        selinux.setfiles(file_contexts, os.fspath(tree), "", exclude_paths=exclude_paths)
 
     labels = options.get("labels", {})
     for path, label in labels.items():
diff --git a/stages/org.osbuild.selinux.meta.json b/stages/org.osbuild.selinux.meta.json
index ea1bb3ef..30dbddae 100644
--- a/stages/org.osbuild.selinux.meta.json
+++ b/stages/org.osbuild.selinux.meta.json
@@ -20,8 +20,17 @@
   "schema_2": {
     "options": {
       "additionalProperties": false,
-      "required": [
-        "file_contexts"
+      "anyOf": [
+        {
+          "required": [
+            "file_contexts"
+          ]
+        },
+        {
+          "required": [
+            "labels"
+          ]
+        }
       ],
       "properties": {
         "file_contexts": {


Test patch:
diff --git a/stages/test/test_selinux.py b/stages/test/test_selinux.py
index af1e1b56..7cb51bcd 100644
--- a/stages/test/test_selinux.py
+++ b/stages/test/test_selinux.py
@@ -10,13 +10,15 @@ from osbuild import testutil
 STAGE_NAME = "org.osbuild.selinux"
 
 
-def get_test_input(test_data, implicit_file_contexts=True):
+def get_test_input(test_data, file_contexts=False, labels=False):
     test_input = {
         "type": STAGE_NAME,
         "options": {}
     }
-    if implicit_file_contexts:
+    if file_contexts:
         test_input["options"]["file_contexts"] = "some-context"
+    if labels:
+        test_input["options"]["labels"] = {"/path/to/file": "label_to_apply"}
 
     test_input["options"].update(test_data)
     return test_input
@@ -33,7 +35,7 @@ def get_test_input(test_data, implicit_file_contexts=True):
     ({"force_autorelabel": "foo"}, "'foo' is not of type 'boolean'"),
 ])
 def test_schema_validation_selinux(stage_schema, test_data, expected_err):
-    res = stage_schema.validate(get_test_input(test_data))
+    res = stage_schema.validate(get_test_input(test_data, file_contexts=True, labels=False))
     if expected_err == "":
         assert res.valid is True, f"err: {[e.as_dict() for e in res.errors]}"
     else:
@@ -41,11 +43,15 @@ def test_schema_validation_selinux(stage_schema, test_data, expected_err):
         testutil.assert_jsonschema_error_contains(res, expected_err, expected_num_errs=1)
 
 
-def test_schema_validation_selinux_file_context_required(stage_schema):
-    res = stage_schema.validate(get_test_input({}, implicit_file_contexts=False))
+def test_schema_validation_selinux_required_options(stage_schema):
+    res = stage_schema.validate(get_test_input({}, file_contexts=False, labels=False))
     assert res.valid is False
-    expected_err = "'file_contexts' is a required property"
+    expected_err = "{} is not valid under any of the given schemas"
     testutil.assert_jsonschema_error_contains(res, expected_err, expected_num_errs=1)
+    res = stage_schema.validate(get_test_input({}, file_contexts=True, labels=False))
+    assert res.valid is True
+    res = stage_schema.validate(get_test_input({}, file_contexts=False, labels=True))
+    assert res.valid is True
 
 
 @patch("osbuild.util.selinux.setfiles")