diff options
Diffstat (limited to 'sb_builder/remote.py')
| -rw-r--r-- | sb_builder/remote.py | 84 |
1 files changed, 84 insertions, 0 deletions
diff --git a/sb_builder/remote.py b/sb_builder/remote.py new file mode 100644 index 0000000..5340f6c --- /dev/null +++ b/sb_builder/remote.py @@ -0,0 +1,84 @@ +# sb-builder - ssh and virsh transport +# Copyright (C) 2026 Danilo M. <danix@danix.xyz> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +"""The only code that talks to the VM: ssh to sbb-remote, and local virsh.""" + +import json +import os +import shlex + +from PyQt6.QtCore import QObject, QProcess, QStandardPaths + +DOWN = "ssh: connection failed" +MISSING = "sbb-remote is not installed on the VM" + + +def parse(rc, out, err): + """Map one sbb-remote call to (data, error).""" + if rc == 255: + return None, DOWN + if rc == 127: + return None, MISSING + try: + data = json.loads(out) + except ValueError: + return None, err.decode(errors="replace").strip() or f"exit {rc}" + if rc != 0 or "error" in data: + return None, data.get("error", f"exit {rc}") + return data, None + + +class Remote(QObject): + def __init__(self, cfg, parent=None): + super().__init__(parent) + self.cfg = cfg # shared dict, updated in place on config reload + run = QStandardPaths.writableLocation(QStandardPaths.StandardLocation.RuntimeLocation) + self.control = os.path.join(run or "/tmp", "sbb-%C") + + def ssh_args(self, *args): + r = self.cfg["remote"] + # ssh joins its args into a remote shell command: quote every one + return ["-o", "ControlMaster=auto", "-o", f"ControlPath={self.control}", + "-o", "ControlPersist=10m", "-o", "BatchMode=yes", "-o", "ConnectTimeout=5", + *r["ssh_options"], r["target"], "sbb-remote", *map(shlex.quote, args)] + + def call(self, cb, *args): + """Run sbb-remote <args>, then cb(data, error).""" + self._run("ssh", self.ssh_args(*args), lambda rc, out, err: cb(*parse(rc, out, err))) + + def virsh(self, cb, *args): + """Run local virsh against the configured libvirt URI, then cb(rc, text).""" + argv = ["-c", self.cfg["vm"]["libvirt_uri"], *args] + self._run("virsh", argv, + lambda rc, out, err: cb(rc, (out or err).decode(errors="replace").strip())) + + def follow(self, jid, offset): + """Start job-follow; the caller wires readyReadStandardOutput and finished.""" + p = QProcess(self) + p.start("ssh", self.ssh_args("job-follow", jid, str(offset))) + return p + + def _run(self, prog, argv, cb): + p = QProcess(self) + + def done(rc, _status=None): + cb(rc, bytes(p.readAllStandardOutput()), bytes(p.readAllStandardError())) + p.deleteLater() + + p.finished.connect(done) + p.errorOccurred.connect( + lambda e: e == QProcess.ProcessError.FailedToStart and done(-1)) + p.start(prog, argv) |
