Repository: osbuild/osbuild
Pull request #529 (2020-10-20 16:05:57)

Auto detect the host runner in modules tests
Instead of hard-coding the use of the `org.osbuild.linux` runner, introduce and use the a new `osbuild.pipeline.detect_host_runner` helper to dynamically detect the runner for the host system. That should fix the tests on RHEL systems, where `python3` is by default not present and even if it is manually installed, is an indirection via alternatives (i.e. a link to `/etc/alternatives`), which must be explicitly configured in the build root container for the host (by the appropriate host runner).

gicmo: Tested this manually on RHEL 8.3 beta and worked as expected.

Patch:
diff --git a/osbuild/pipeline.py b/osbuild/pipeline.py
index 118fcf15..1e6f8434 100644
--- a/osbuild/pipeline.py
+++ b/osbuild/pipeline.py
@@ -360,6 +360,12 @@ class Pipeline:
         return results
 
 
+def detect_host_runner():
+    """Use os-release(5) to detect the runner for the host"""
+    osname = osrelease.describe_os(*osrelease.DEFAULT_PATHS)
+    return "org.osbuild." + osname
+
+
 def load_build(description, sources_options):
     pipeline = description.get("pipeline")
     if pipeline:
@@ -375,7 +381,7 @@ def load(description, sources_options):
     if build:
         build_pipeline, runner = load_build(build, sources_options)
     else:
-        build_pipeline, runner = None, "org.osbuild." + osrelease.describe_os("/etc/os-release", "/usr/lib/os-release")
+        build_pipeline, runner = None, detect_host_runner()
 
     pipeline = Pipeline(runner, build_pipeline)
 
diff --git a/osbuild/util/osrelease.py b/osbuild/util/osrelease.py
index 0f5d55f8..24e003b9 100644
--- a/osbuild/util/osrelease.py
+++ b/osbuild/util/osrelease.py
@@ -7,6 +7,13 @@ related documentation can be found in `os-release(5)`.
 import os
 
 
+# The default paths where os-release is located, as per os-release(5)
+DEFAULT_PATHS = [
+    "/etc/os-release",
+    "/usr/lib/os-release"
+]
+
+
 def parse_files(*paths):
     """Read Operating System Information from `os-release`
 


Test patch:
diff --git a/test/mod/test_buildroot.py b/test/mod/test_buildroot.py
index 60de5732..9558fb2d 100644
--- a/test/mod/test_buildroot.py
+++ b/test/mod/test_buildroot.py
@@ -10,6 +10,7 @@ import unittest
 
 from osbuild.buildroot import BuildRoot
 from osbuild.monitor import LogMonitor, NullMonitor
+from osbuild.pipeline import detect_host_runner
 from .. import test
 
 
@@ -23,13 +24,13 @@ class TestBuildRoot(test.TestBase):
         self.tmp.cleanup()
 
     def test_basic(self):
-        runner = "org.osbuild.linux"
+        runner = detect_host_runner()
         libdir = os.path.abspath(os.curdir)
         var = pathlib.Path(self.tmp.name, "var")
         var.mkdir()
 
         monitor = NullMonitor(sys.stderr.fileno())
-        with BuildRoot("/", runner, libdir=libdir, var=var) as root:
+        with BuildRoot("/", runner, libdir, var=var) as root:
 
             r = root.run(["/usr/bin/true"], monitor)
             self.assertEqual(r.returncode, 0)
@@ -49,7 +50,7 @@ class TestBuildRoot(test.TestBase):
 
         logfile = os.path.join(self.tmp.name, "log.txt")
 
-        with BuildRoot("/", runner, libdir=libdir, var=var) as root, \
+        with BuildRoot("/", runner, libdir, var=var) as root, \
              open(logfile, "w") as log:
 
             monitor = LogMonitor(log.fileno())
@@ -64,7 +65,7 @@ class TestBuildRoot(test.TestBase):
         self.assertEqual(log, r.output)
 
     def test_output(self):
-        runner = "org.osbuild.linux"
+        runner = detect_host_runner()
         libdir = os.path.abspath(os.curdir)
         var = pathlib.Path(self.tmp.name, "var")
         var.mkdir()
@@ -72,16 +73,16 @@ class TestBuildRoot(test.TestBase):
         data = "42. cats are superior to dogs"
 
         monitor = NullMonitor(sys.stderr.fileno())
-        with BuildRoot("/", runner, libdir=libdir, var=var) as root:
+        with BuildRoot("/", runner, libdir, var=var) as root:
 
             r = root.run(["/usr/bin/echo", data], monitor)
             self.assertEqual(r.returncode, 0)
 
-        self.assertEqual(data, r.output.strip())
+        self.assertIn(data, r.output.strip())
 
     @unittest.skipUnless(test.TestBase.have_test_data(), "no test-data access")
     def test_bind_mounts(self):
-        runner = "org.osbuild.linux"
+        runner = detect_host_runner()
         libdir = os.path.abspath(os.curdir)
         var = pathlib.Path(self.tmp.name, "var")
         var.mkdir()
@@ -92,7 +93,7 @@ class TestBuildRoot(test.TestBase):
         scripts = os.path.join(self.locate_test_data(), "scripts")
 
         monitor = NullMonitor(sys.stderr.fileno())
-        with BuildRoot("/", runner, libdir=libdir, var=var) as root:
+        with BuildRoot("/", runner, libdir, var=var) as root:
 
             ro_binds = [f"{scripts}:/scripts"]
 
@@ -118,7 +119,7 @@ class TestBuildRoot(test.TestBase):
         # because RPM and other tools must not assume the policy
         # of the host is the valid policy
 
-        runner = "org.osbuild.linux"
+        runner = detect_host_runner()
         libdir = os.path.abspath(os.curdir)
         var = pathlib.Path(self.tmp.name, "var")
         var.mkdir()
@@ -126,7 +127,7 @@ class TestBuildRoot(test.TestBase):
         scripts = os.path.join(self.locate_test_data(), "scripts")
 
         monitor = NullMonitor(sys.stderr.fileno())
-        with BuildRoot("/", runner, libdir=libdir, var=var) as root:
+        with BuildRoot("/", runner, libdir, var=var) as root:
 
             ro_binds = [f"{scripts}:/scripts"]
 
diff --git a/test/mod/test_monitor.py b/test/mod/test_monitor.py
index 6969977c..d0eac1cb 100644
--- a/test/mod/test_monitor.py
+++ b/test/mod/test_monitor.py
@@ -12,6 +12,7 @@ from collections import defaultdict
 import osbuild
 import osbuild.meta
 from osbuild.monitor import LogMonitor
+from osbuild.pipeline import detect_host_runner
 from .. import test
 
 
@@ -53,7 +54,8 @@ class TestMonitor(unittest.TestCase):
     @unittest.skipUnless(test.TestBase.can_bind_mount(), "root-only")
     def test_log_monitor_vfuncs(self):
         # Checks the basic functioning of the LogMonitor
-        pipeline = osbuild.Pipeline("org.osbuild.linux")
+        runner = detect_host_runner()
+        pipeline = osbuild.Pipeline(runner=runner)
         pipeline.add_stage("org.osbuild.noop", {}, {
             "isthisthereallife": False
         })
@@ -83,7 +85,8 @@ class TestMonitor(unittest.TestCase):
     @unittest.skipUnless(test.TestBase.can_bind_mount(), "root-only")
     def test_monitor_integration(self):
         # Checks the monitoring API is called properly from the pipeline
-        pipeline = osbuild.Pipeline("org.osbuild.linux")
+        runner = detect_host_runner()
+        pipeline = osbuild.Pipeline(runner=runner)
         pipeline.add_stage("org.osbuild.noop", {}, {
             "isthisthereallife": False
         })
diff --git a/test/mod/test_osbuild.py b/test/mod/test_osbuild.py
index 17ac30da..f99279c1 100644
--- a/test/mod/test_osbuild.py
+++ b/test/mod/test_osbuild.py
@@ -12,6 +12,7 @@ import unittest
 import osbuild
 import osbuild.meta
 from osbuild.monitor import NullMonitor
+from osbuild.pipeline import detect_host_runner
 from .. import test
 
 
@@ -52,7 +53,7 @@ class TestDescriptions(unittest.TestCase):
             data = pathlib.Path(tmpdir, "data")
             cache = pathlib.Path(tmpdir, "cache")
             root = pathlib.Path("/")
-            runner = "org.osbuild.linux"
+            runner = detect_host_runner()
             monitor = NullMonitor(sys.stderr.fileno())
             libdir = os.path.abspath(os.curdir)
 
@@ -86,7 +87,7 @@ class TestDescriptions(unittest.TestCase):
             cache = pathlib.Path(tmpdir, "cache")
             output = pathlib.Path(tmpdir, "output")
             root = pathlib.Path("/")
-            runner = "org.osbuild.linux"
+            runner = detect_host_runner()
             monitor = NullMonitor(sys.stderr.fileno())
             libdir = os.path.abspath(os.curdir)