From 551faf2d61c82b1ac914e27e2d2a4476692c7d1d Mon Sep 17 00:00:00 2001 From: David Rheinsberg Date: Wed, 15 Apr 2020 08:30:40 +0200 Subject: [PATCH] osbuild: add --output-directory=DIR Add a new output-directory argument which specifies where to store result objects. For now, this is purely optional and simply copies from the old `output_id` into the specified directory. This allows a backwards compatible transition towards removing any external access to the osbuild cache. Note that this has still lots of room for improvements: * We only support assembler-output for now, but we could also easily support entire trees as output, in case no assembler was selected. Alternatively, we could introduce a "copy" assembler, that just outputs the input tree. * This parameter is optional, but should really be mandatory. There is little reason to have the default behavior just dropping any generated content. This would be a breaking change, though. * We could move data out of a temporary object-store entry, rather than copy it. But again, for backwards-compatibility, we leave the latest store-object intact and do not move things out of it. * We could now transition towards never committing anything to the store, not even output IDs, unless explicitly checkpointed. --- docs/osbuild.1.rst | 1 + osbuild/__main__.py | 5 ++++- osbuild/pipeline.py | 41 +++++++++++++++++++++++------------------ 3 files changed, 28 insertions(+), 19 deletions(-) diff --git a/docs/osbuild.1.rst b/docs/osbuild.1.rst index 7c1fa141..d45630dc 100644 --- a/docs/osbuild.1.rst +++ b/docs/osbuild.1.rst @@ -50,6 +50,7 @@ is not listed here, **osbuild** will deny startup and exit with an error. --checkpoint=CHECKPOINT stage to commit to the object store during build (can be passed multiple times) --json output results in JSON format +--output-directory=DIR directory where result objects are stored MANIFEST ======== diff --git a/osbuild/__main__.py b/osbuild/__main__.py index 98c9f1ee..c3d23193 100755 --- a/osbuild/__main__.py +++ b/osbuild/__main__.py @@ -50,6 +50,8 @@ def main(): 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 == "-": @@ -91,7 +93,8 @@ def main(): args.store, interactive=not args.json, libdir=args.libdir, - secrets=secrets + secrets=secrets, + output_directory=args.output_directory ) except KeyboardInterrupt: print() diff --git a/osbuild/pipeline.py b/osbuild/pipeline.py index 9eda2e0f..ee6dd361 100644 --- a/osbuild/pipeline.py +++ b/osbuild/pipeline.py @@ -340,37 +340,42 @@ class Pipeline: results["output_id"] = self.output_id return results - def run(self, store, interactive=False, libdir=None, secrets=None): + def run(self, store, interactive=False, libdir=None, secrets=None, output_directory=None): os.makedirs("/run/osbuild", exist_ok=True) results = {} with objectstore.ObjectStore(store) as object_store: - # if the final result is already in the store, exit - # early and don't attempt to build the tree, which - # in turn might not be in the store and would in that - # case be build but not be used + # If the final result is already in the store, no need to attempt + # building it. Just fetch the cached information. If the associated + # tree exists, we return it as well, but we do not care if it is + # missing, since it is not a mandatory part of the result and would + # usually be needless overhead. if object_store.contains(self.output_id): results = {"output_id": self.output_id, "success": True} if object_store.contains(self.tree_id): results["tree_id"] = self.tree_id - return results + else: + results, build_tree, tree = self.build_stages(object_store, + interactive, + libdir, + secrets) - results, build_tree, tree = self.build_stages(object_store, - interactive, - libdir, - secrets) + if not results["success"]: + return results - if not results["success"]: - return results + r = self.assemble(object_store, + build_tree, + tree, + interactive, + libdir) - r = self.assemble(object_store, - build_tree, - tree, - interactive, - libdir) + results.update(r) # This will also update 'success' - results.update(r) # This will also update 'success' + if results["success"] and output_directory is not None: + output_source = object_store.resolve_ref(results["output_id"]) + if output_source is not None: + subprocess.run(["cp", "--reflink=auto", "-a", f"{output_source}/.", output_directory], check=True) return results