osbuild: move run() into osbuild.py

This allows for running a pipline from python and for non-interactive
mode, in which all output is captured.
This commit is contained in:
Lars Karlitski 2019-06-30 19:59:08 +02:00 committed by Tom Gundersen
parent ecaed3bbfa
commit 767b249b2d
2 changed files with 57 additions and 35 deletions

32
osbuild
View file

@ -13,33 +13,6 @@ BOLD = "\033[1m"
RED = "\033[31m"
def run_interactive(pipeline_path, input_dir, output_dir):
with open(pipeline_path) as f:
pipeline = json.load(f)
with osbuild.BuildRoot() as buildroot, osbuild.tmpfs() as tree:
for i, stage in enumerate(pipeline["stages"], start=1):
name = stage["name"]
options = stage.get("options", {})
print()
print(f"{RESET}{BOLD}{i}. {name}{RESET} " + json.dumps(options or {}, indent=2))
print("Inspect with:")
print(f"\t# nsenter -a --wd=/root -t `machinectl show {buildroot.machine_name} -p Leader --value`")
print()
buildroot.run_stage(stage, tree, input_dir)
assembler = pipeline.get("assembler")
if assembler:
name = assembler["name"]
options = assembler.get("options", {})
print()
print(f"{RESET}{BOLD}Assembling: {name}{RESET} " + json.dumps(options or {}, indent=2))
print("Inspect with:")
print(f"\t# nsenter -a --wd=/root -t `machinectl show {buildroot.machine_name} -p Leader --value`")
print()
buildroot.run_assembler(assembler, tree, input_dir, output_dir)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Build operating system images")
parser.add_argument("pipeline_path", metavar="PIPELINE",
@ -53,8 +26,11 @@ if __name__ == "__main__":
os.makedirs("/run/osbuild", exist_ok=True)
with open(args.pipeline_path) as f:
pipeline = json.load(f)
try:
run_interactive(args.pipeline_path, args.input_dir, args.output_dir)
osbuild.run(pipeline, args.input_dir, args.output_dir, interactive=True)
except KeyboardInterrupt:
print()
print(f"{RESET}{BOLD}{RED}Aborted{RESET}")

View file

@ -9,19 +9,25 @@ import tempfile
__all__ = [
"StageFailed",
"BuildRoot",
"tmpfs"
"tmpfs",
"run"
]
RESET = "\033[0m"
BOLD = "\033[1m"
libdir = os.path.dirname(__file__)
if not os.path.exists(f"{libdir}/stages"):
libdir = f"{sys.prefix}/lib"
class StageFailed(Exception):
def __init__(self, stage, returncode):
def __init__(self, stage, returncode, output):
self.stage = stage
self.returncode = returncode
self.output = output
class tmpfs:
@ -105,7 +111,7 @@ class BuildRoot:
raise ValueError(f"{r} tries to bind to a different location")
return resources
def run_stage(self, stage, tree, input_dir=None):
def run_stage(self, stage, tree, input_dir=None, interactive=False):
name = stage["name"]
args = {
"tree": "/run/osbuild/tree",
@ -122,9 +128,16 @@ class BuildRoot:
args["input_dir"] = "/run/osbuild/input"
try:
self.run([f"/run/osbuild/{name}"], binds=binds, readonly_binds=robinds, input=json.dumps(args), encoding="utf-8", check=True)
self.run([f"/run/osbuild/{name}"],
binds=binds,
readonly_binds=robinds,
input=json.dumps(args),
encoding="utf-8",
stdout=subprocess.PIPE if not interactive else None,
stderr=subprocess.STDOUT if not interactive else None,
check=True)
except subprocess.CalledProcessError as error:
raise StageFailed(name, error.returncode)
raise StageFailed(name, error.returncode, error.stdout)
def run_assembler(self, assembler, tree, input_dir=None, output_dir=None):
if output_dir and not os.path.exists(output_dir):
@ -150,9 +163,16 @@ class BuildRoot:
args["output_dir"] = "/run/osbuild/output"
try:
self.run([f"/run/osbuild/{name}"], binds=binds, readonly_binds=robinds, input=json.dumps(args), encoding="utf-8", check=True)
self.run([f"/run/osbuild/{name}"],
binds=binds,
readonly_binds=robinds,
input=json.dumps(args),
encoding="utf-8",
stdout=subprocess.PIPE if not interactive else None,
stderr=subprocess.STDOUT if not interactive else None,
check=True)
except subprocess.CalledProcessError as error:
raise StageFailed(name, error.returncode)
raise StageFailed(name, error.returncode, error.stdout)
def __del__(self):
self.unmount()
@ -162,3 +182,29 @@ class BuildRoot:
def __exit__(self, exc_type, exc_value, exc_tb):
self.unmount()
def print_header(title, options, machine_name):
print()
print(f"{RESET}{BOLD}{title}{RESET} " + json.dumps(options or {}, indent=2))
print("Inspect with:")
print(f"\t# nsenter -a --wd=/root -t `machinectl show {machine_name} -p Leader --value`")
print()
def run(pipeline, input_dir, output_dir, interactive=False):
with BuildRoot() as buildroot, tmpfs() as tree:
for i, stage in enumerate(pipeline["stages"], start=1):
name = stage["name"]
options = stage.get("options", {})
if interactive:
print_header(f"{i}. {name}", options, buildroot.machine_name)
buildroot.run_stage(stage, tree, input_dir, interactive)
assembler = pipeline.get("assembler")
if assembler:
name = assembler["name"]
options = assembler.get("options", {})
if interactive:
print_header(f"Assembling: {name}", options, buildroot.machine_name)
buildroot.run_assembler(assembler, tree, input_dir, output_dir, interactive)