osbuild: extract CLI to prepare for additional entrypoints
This extracts the CLI entrypoint into `main_cli.py` and prepares the codebase for the introduction of additional entrypoints. This should not contain any functional changes. The idea behind this is to add `main_api.py` (and maybe more in the future), which will be similar to `main_cli.py` but contain the `osbuild-api` entrypoint. This will make all entrypoints nicely symetric and the only difference will be `setup.py` selecting the right entrypoint for each executable, as well as `__main__.py` selecting the entrypoint for the module itself (which we will keep to the CLI for compatibility).
This commit is contained in:
parent
20cf5dba6a
commit
421414ef0b
4 changed files with 153 additions and 114 deletions
|
|
@ -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
|
from .pipeline import Assembler, load, load_build, Pipeline, Stage
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,118 +1,12 @@
|
||||||
import argparse
|
"""OSBuild Main
|
||||||
import json
|
|
||||||
import os
|
This specifies the entrypoint of the osbuild module when run as executable. For
|
||||||
|
compatibility we will continue to run the CLI.
|
||||||
|
"""
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import osbuild
|
|
||||||
|
|
||||||
|
from osbuild.main_cli import main_cli as main
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
|
||||||
133
osbuild/main_cli.py
Normal file
133
osbuild/main_cli.py
Normal file
|
|
@ -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))
|
||||||
4
setup.py
4
setup.py
|
|
@ -7,6 +7,8 @@ setuptools.setup(
|
||||||
packages=["osbuild", "osbuild.util"],
|
packages=["osbuild", "osbuild.util"],
|
||||||
license='Apache-2.0',
|
license='Apache-2.0',
|
||||||
entry_points={
|
entry_points={
|
||||||
"console_scripts": ["osbuild = osbuild.__main__:main"]
|
"console_scripts": [
|
||||||
|
"osbuild = osbuild.main_cli:main_cli"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue