diff --git a/osbuild/__init__.py b/osbuild/__init__.py index 49007abf..fed324ef 100644 --- a/osbuild/__init__.py +++ b/osbuild/__init__.py @@ -1,3 +1,13 @@ +"""OSBuild Module + +The `osbuild` module provides access to the internal features of OSBuild. It +provides parsers for the input and output formats of osbuild, access to shared +infrastructure of osbuild stages, as well as a pipeline executor. + +The utility module `osbuild.util` provides access to common functionality +independent of osbuild but used across the osbuild codebase. +""" + from .pipeline import Assembler, load, load_build, Pipeline, Stage diff --git a/osbuild/__main__.py b/osbuild/__main__.py index c3d23193..d374c67b 100755 --- a/osbuild/__main__.py +++ b/osbuild/__main__.py @@ -1,118 +1,12 @@ -import argparse -import json -import os +"""OSBuild Main + +This specifies the entrypoint of the osbuild module when run as executable. For +compatibility we will continue to run the CLI. +""" + import sys -import osbuild - -RESET = "\033[0m" -BOLD = "\033[1m" -RED = "\033[31m" - - -def mark_checkpoints(pipeline, checkpoints): - points = set(checkpoints) - - def mark_stage(stage): - c = stage.id - if c in points: - stage.checkpoint = True - points.remove(c) - - def mark_pipeline(pl): - for stage in pl.stages: - mark_stage(stage) - if pl.build: - mark_pipeline(pl.build) - - mark_pipeline(pipeline) - return points - - -# pylint: disable=too-many-branches -# pylint: disable=too-many-statements -def main(): - parser = argparse.ArgumentParser(description="Build operating system images") - parser.add_argument("manifest_path", metavar="MANIFEST", - help="json file containing the manifest that should be built, or a '-' to read from stdin") - parser.add_argument("--build-env", metavar="FILE", type=os.path.abspath, - help="json file containing a description of the build environment") - parser.add_argument("--store", metavar="DIRECTORY", type=os.path.abspath, - default=".osbuild", - help="directory where intermediary os trees are stored") - parser.add_argument("--sources", metavar="FILE", type=os.path.abspath, - help="json file containing a dictionary of source configuration") - parser.add_argument("--secrets", metavar="FILE", type=os.path.abspath, - help="json file containing a dictionary of secrets that are passed to sources") - parser.add_argument("-l", "--libdir", metavar="DIRECTORY", type=os.path.abspath, - help="the directory containing stages, assemblers, and the osbuild library") - parser.add_argument("--checkpoint", metavar="ID", action="append", type=str, default=None, - help="stage to commit to the object store during build (can be passed multiple times)") - parser.add_argument("--json", action="store_true", - help="output results in JSON format") - parser.add_argument("--output-directory", metavar="DIRECTORY", type=os.path.abspath, - help="directory where result objects are stored") - args = parser.parse_args() - - if args.manifest_path == "-": - f = sys.stdin - else: - f = open(args.manifest_path) - manifest = json.load(f) - f.close() - - pipeline = manifest.get("pipeline", {}) - sources_options = manifest.get("sources", {}) - - if args.sources: - with open(args.sources) as f: - sources_options = json.load(f) - - pipeline = osbuild.load(pipeline, sources_options) - - if args.build_env: - with open(args.build_env) as f: - build_pipeline, runner = osbuild.load_build(json.load(f), sources_options) - pipeline.prepend_build_env(build_pipeline, runner) - - secrets = {} - if args.secrets: - with open(args.secrets) as f: - secrets = json.load(f) - - if args.checkpoint: - missed = mark_checkpoints(pipeline, args.checkpoint) - if missed: - for checkpoint in missed: - print(f"Checkpoint {BOLD}{checkpoint}{RESET} not found!") - print(f"{RESET}{BOLD}{RED}Failed{RESET}") - return 1 - - try: - r = pipeline.run( - args.store, - interactive=not args.json, - libdir=args.libdir, - secrets=secrets, - output_directory=args.output_directory - ) - except KeyboardInterrupt: - print() - print(f"{RESET}{BOLD}{RED}Aborted{RESET}") - return 130 - - if args.json: - json.dump(r, sys.stdout) - sys.stdout.write("\n") - else: - if r["success"]: - print("tree id:", pipeline.tree_id) - print("output id:", pipeline.output_id) - else: - print() - print(f"{RESET}{BOLD}{RED}Failed{RESET}") - - return 0 if r["success"] else 1 +from osbuild.main_cli import main_cli as main if __name__ == "__main__": diff --git a/osbuild/main_cli.py b/osbuild/main_cli.py new file mode 100644 index 00000000..66269747 --- /dev/null +++ b/osbuild/main_cli.py @@ -0,0 +1,133 @@ +"""Entrypoints for osbuild + +This module contains the application and API entrypoints of `osbuild`, the +command-line-interface to osbuild. The `osbuild_cli()` entrypoint can be safely +used from tests to run the cli. +""" + + +import argparse +import json +import os +import sys +import osbuild + + +RESET = "\033[0m" +BOLD = "\033[1m" +RED = "\033[31m" + + +def mark_checkpoints(pipeline, checkpoints): + points = set(checkpoints) + + def mark_stage(stage): + c = stage.id + if c in points: + stage.checkpoint = True + points.remove(c) + + def mark_pipeline(pl): + for stage in pl.stages: + mark_stage(stage) + if pl.build: + mark_pipeline(pl.build) + + mark_pipeline(pipeline) + return points + + +# pylint: disable=too-many-branches +# pylint: disable=too-many-statements +def osbuild_cli(*, sys_argv=[]): + parser = argparse.ArgumentParser(description="Build operating system images") + parser.add_argument("manifest_path", metavar="MANIFEST", + help="json file containing the manifest that should be built, or a '-' to read from stdin") + parser.add_argument("--build-env", metavar="FILE", type=os.path.abspath, + help="json file containing a description of the build environment") + parser.add_argument("--store", metavar="DIRECTORY", type=os.path.abspath, + default=".osbuild", + help="directory where intermediary os trees are stored") + parser.add_argument("--sources", metavar="FILE", type=os.path.abspath, + help="json file containing a dictionary of source configuration") + parser.add_argument("--secrets", metavar="FILE", type=os.path.abspath, + help="json file containing a dictionary of secrets that are passed to sources") + parser.add_argument("-l", "--libdir", metavar="DIRECTORY", type=os.path.abspath, + help="the directory containing stages, assemblers, and the osbuild library") + parser.add_argument("--checkpoint", metavar="ID", action="append", type=str, default=None, + help="stage to commit to the object store during build (can be passed multiple times)") + parser.add_argument("--json", action="store_true", + help="output results in JSON format") + parser.add_argument("--output-directory", metavar="DIRECTORY", type=os.path.abspath, + help="directory where result objects are stored") + args = parser.parse_args(sys_argv[1:]) + + if args.manifest_path == "-": + f = sys.stdin + else: + f = open(args.manifest_path) + manifest = json.load(f) + f.close() + + pipeline = manifest.get("pipeline", {}) + sources_options = manifest.get("sources", {}) + + if args.sources: + with open(args.sources) as f: + sources_options = json.load(f) + + pipeline = osbuild.load(pipeline, sources_options) + + if args.build_env: + with open(args.build_env) as f: + build_pipeline, runner = osbuild.load_build(json.load(f), sources_options) + pipeline.prepend_build_env(build_pipeline, runner) + + secrets = {} + if args.secrets: + with open(args.secrets) as f: + secrets = json.load(f) + + if args.checkpoint: + missed = mark_checkpoints(pipeline, args.checkpoint) + if missed: + for checkpoint in missed: + print(f"Checkpoint {BOLD}{checkpoint}{RESET} not found!") + print(f"{RESET}{BOLD}{RED}Failed{RESET}") + return 1 + + try: + r = pipeline.run( + args.store, + interactive=not args.json, + libdir=args.libdir, + secrets=secrets, + output_directory=args.output_directory + ) + except KeyboardInterrupt: + print() + print(f"{RESET}{BOLD}{RED}Aborted{RESET}") + return 130 + + if args.json: + json.dump(r, sys.stdout) + sys.stdout.write("\n") + else: + if r["success"]: + print("tree id:", pipeline.tree_id) + print("output id:", pipeline.output_id) + else: + print() + print(f"{RESET}{BOLD}{RED}Failed{RESET}") + + return 0 if r["success"] else 1 + + +def main_cli(): + """osbuild-cli entrypoint + + This is the entrypoint used by the `osbuild` executable. We simply fetch the + global configuration and parameters necessary and invoke the API entrypoint. + """ + + sys.exit(osbuild_cli(sys_argv=sys.argv)) diff --git a/setup.py b/setup.py index 88c22148..c870903f 100644 --- a/setup.py +++ b/setup.py @@ -7,6 +7,8 @@ setuptools.setup( packages=["osbuild", "osbuild.util"], license='Apache-2.0', entry_points={ - "console_scripts": ["osbuild = osbuild.__main__:main"] + "console_scripts": [ + "osbuild = osbuild.main_cli:main_cli" + ] }, )