diff --git a/doc/configuration.rst b/doc/configuration.rst index b965912b..3cab7378 100644 --- a/doc/configuration.rst +++ b/doc/configuration.rst @@ -867,6 +867,10 @@ Options Available methods are: * ``local`` -- runroot tasks are run locally * ``koji`` -- runroot tasks are run in Koji + * ``openssh`` -- runroot tasks are run on remote machine connected using OpenSSH. + The ``runroot_ssh_hostnames`` for each architecture must be set and the + user under which Pungi runs must be configured to login as ``runroot_ssh_username`` + using the SSH key. **runroot_channel** (*str*) -- name of koji channel @@ -885,6 +889,14 @@ Options * ``ostree`` * ``ostree_installer`` +**runroot_ssh_username** + (*str*) -- For ``openssh`` runroot method, configures the username used to login + the remote machine to run the runroot task. Defaults to "root". + +**runroot_ssh_hostnames** + (*dict*) -- For ``openssh`` runroot method, defines the hostname for each + architecture on which the runroot task should be running. Format: + ``{"x86_64": "runroot-x86-64.localhost.tld", ...}`` Example ------- diff --git a/pungi/checks.py b/pungi/checks.py index 715fc85f..9976d0b1 100644 --- a/pungi/checks.py +++ b/pungi/checks.py @@ -562,7 +562,15 @@ def make_schema(): }, "runroot_method": { "type": "string", - "enum": ["local", "koji"], + "enum": ["local", "koji", "openssh"], + }, + "runroot_ssh_username": { + "type": "string", + "default": "root", + }, + "runroot_ssh_hostnames": { + "type": "object", + "default": {}, }, "create_jigdo": { "type": "boolean", diff --git a/pungi/runroot.py b/pungi/runroot.py index 26673ac7..edf9b49b 100644 --- a/pungi/runroot.py +++ b/pungi/runroot.py @@ -78,10 +78,36 @@ class Runroot(kobo.log.LoggingBase): output = koji_wrapper.run_runroot_cmd(koji_cmd, log_file=log_file) if output["retcode"] != 0: - raise RuntimeError("Runroot task failed: %s. See %s for more details." - % (output["task_id"], log_file)) + raise RuntimeError( + "Runroot task failed: %s. See %s for more details." + % (output["task_id"], log_file) + ) self._result = output + def _run_openssh(self, command, log_file=None, arch=None, **kwargs): + """ + Runs the runroot command on remote machine using ssh. + """ + runroot_ssh_hostnames = self.compose.conf.get("runroot_ssh_hostnames", {}) + if arch not in runroot_ssh_hostnames: + raise ValueError("The arch %r not in runroot_ssh_hostnames." % arch) + + hostname = runroot_ssh_hostnames[arch] + user = self.compose.conf.get("runroot_ssh_username", "root") + + ssh_cmd = ["ssh", "-oBatchMode=yes", "-n", "-l", user, hostname, command] + run(ssh_cmd, show_cmd=True, logfile=log_file) + + # Get the buildroot RPMs. + ssh_cmd = ["ssh", "-oBatchMode=yes", "-n", "-l", user, hostname, + "rpm -qa --qf='%{name}-%{version}-%{release}.%{arch}\n'"] + output = run(ssh_cmd, show_cmd=True)[1] + self._result = [] + for i in output.splitlines(): + if not i: + continue + self._result.append(i) + def run(self, command, log_file=None, packages=None, arch=None, output_dir=None, **kwargs): """ @@ -112,6 +138,10 @@ class Runroot(kobo.log.LoggingBase): self._run_koji( command, log_file=log_file, packages=packages, arch=arch, output_dir=output_dir, **kwargs) + elif self.runroot_method == "openssh": + self._run_openssh( + command, log_file=log_file, packages=packages, arch=arch, + output_dir=output_dir, **kwargs) else: raise ValueError("Unknown runroot_method %r." % self.runroot_method) @@ -127,12 +157,17 @@ class Runroot(kobo.log.LoggingBase): :return: List of RPMs in buildroot in which the runroot task run. """ if not self._result: - raise ValueError("Runroot.get_build_rpms before runroot task finished.") + raise ValueError( + "Runroot.get_buildroot_rpms called before runroot task " + "finished.") if self.runroot_method in ["local", "koji"]: if self.runroot_method == "local": task_id = None else: task_id = self._result["task_id"] return kojiwrapper.get_buildroot_rpms(self.compose, task_id) + elif self.runroot_method == "openssh": + # For openssh runroot_method, the result is list of buildroot_rpms. + return self._result else: raise ValueError("Unknown runroot_method %r." % self.runroot_method) diff --git a/tests/test_runroot.py b/tests/test_runroot.py new file mode 100644 index 00000000..153b82eb --- /dev/null +++ b/tests/test_runroot.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- + +import mock +import os +import sys + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) + +from pungi.runroot import Runroot +from tests import helpers + + +class TestRunrootOpenSSH(helpers.PungiTestCase): + def setUp(self): + super(TestRunrootOpenSSH, self).setUp() + self.compose = helpers.DummyCompose(self.topdir, { + "runroot": True, + "runroot_method": "openssh", + "runroot_ssh_user": "root", + "runroot_ssh_hostnames": { + "x86_64": "localhost" + } + }) + + self.runroot = Runroot(self.compose) + + def test_get_runroot_method(self): + method = self.runroot.get_runroot_method() + self.assertEqual(method, "openssh") + + @mock.patch("pungi.runroot.run") + def test_run(self, run): + self.runroot.run("df -h", log_file="/foo/runroot.log", arch="x86_64") + run.assert_has_calls([ + mock.call( + ['ssh', '-oBatchMode=yes', '-n', '-l', 'root', 'localhost', + 'df -h'], logfile='/foo/runroot.log', show_cmd=True), + mock.call( + ['ssh', '-oBatchMode=yes', '-n', '-l', 'root', 'localhost', + "rpm -qa --qf='%{name}-%{version}-%{release}.%{arch}\n'"], + show_cmd=True) + ]) + + @mock.patch("pungi.runroot.run") + def test_get_buildroot_rpms(self, run): + # Run the runroot task at first. + run.return_value = (0, "foo-1-1.fc29.noarch\nbar-1-1.fc29.noarch\n") + self.runroot.run("df -h", log_file="/foo/runroot.log", arch="x86_64") + + rpms = self.runroot.get_buildroot_rpms() + self.assertEqual( + set(rpms), set(["foo-1-1.fc29.noarch", "bar-1-1.fc29.noarch"]))