From 9faa39b400a155c9dddeea4fe712301462093d4f Mon Sep 17 00:00:00 2001 From: Helmut Grohne Date: Tue, 11 May 2021 14:52:29 +0200 Subject: add ssh remote backend and streamapi wrapper The ssh backend calls into another host and passes all information on stdin/stdout/stderr. To do so, it uses a streamapi wrapper on the remote side that itself calls into a regular backend. --- mdbp/streamapi.py | 73 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 mdbp/streamapi.py (limited to 'mdbp/streamapi.py') diff --git a/mdbp/streamapi.py b/mdbp/streamapi.py new file mode 100644 index 0000000..8f67366 --- /dev/null +++ b/mdbp/streamapi.py @@ -0,0 +1,73 @@ +#!/usr/bin/python3 +# SPDX-License-Identifier: MIT +"""mdbp frontend without filesystem interaction. It basically wraps another +backend and is used as frontend from the ssh backend on the remote side. +Differences to the regular backend API: + * It expects an uncompressed tar file on stdin. The first member must be named + "build.json". Any other members must be regular files. The build.json file + should lack the .output.directory and if an .input.dscpath is given, it + should not contain any slashes. + * The build log is issued on stderr instead of stdout. + * All the requested artifacts are emitted as a tar stream on stdout. +""" + +import argparse +import json +import pathlib +import subprocess +import sys +import tarfile +import tempfile + +from .common import json_load, buildjson_validate, buildjson_patch_relative, \ + tar_add + +def main() -> None: + """Entry point for mdbp-streamapi wrapper""" + parser = argparse.ArgumentParser() + parser.add_argument("command", nargs=argparse.REMAINDER) + args = parser.parse_args() + if not args.command: + parser.error("missing command") + + with tempfile.TemporaryDirectory() as tdirname: + indir = pathlib.Path(tdirname) / "input" + outdir = pathlib.Path(tdirname) / "output" + indir.mkdir() + outdir.mkdir() + with tarfile.open(fileobj=sys.stdin.buffer, mode="r|") as intar: + seenjson = False + for member in intar: + if "/" in member.name or not member.isfile(): + raise ValueError("expected flat tar as input") + if seenjson: + intar.extract(member, indir, set_attrs=False) + continue + if member.name != "build.json": + raise ValueError("first input member must be build.json") + jsonfileobj = intar.extractfile(member) + # We already checked .isfile(), but mypy doesn't know. + assert jsonfileobj is not None + build = json_load(jsonfileobj) + build["output"]["directory"] = str(outdir) + buildjson_validate(build) + buildjson_patch_relative(build, indir) + (indir / "build.json").write_text(json.dumps(build)) + seenjson = True + if not seenjson: + raise ValueError("input is an empty tar archive") + proc = subprocess.Popen([*args.command, str(indir / "build.json")], + stdout=sys.stderr + if build["output"].get("log", True) + else subprocess.DEVNULL, + stderr=None if build["output"].get("log", True) + else subprocess.DEVNULL) + code = proc.wait() + if code != 0: + sys.exit(code) + with tarfile.open(fileobj=sys.stdout.buffer, mode="w|") as outtar: + for elem in outdir.iterdir(): + tar_add(outtar, elem) + +if __name__ == "__main__": + main() -- cgit v1.2.3