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:
parent
ecaed3bbfa
commit
767b249b2d
2 changed files with 57 additions and 35 deletions
32
osbuild
32
osbuild
|
|
@ -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}")
|
||||
|
|
|
|||
60
osbuild.py
60
osbuild.py
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue