osbuild: store outputs in objectstore

Treat outputs like we treat trees: store them in the object store. This
simplifies using osbuild and allows returning a cached version if one is
available.

This makes the `--output` parameter redundant. Remove it.
This commit is contained in:
Lars Karlitski 2019-09-25 21:56:47 +02:00
parent cb173f7d3c
commit 83475cc9f4
9 changed files with 27 additions and 34 deletions

View file

@ -21,8 +21,8 @@ jobs:
- name: pipeline-noop - name: pipeline-noop
before_install: sudo apt-get install -y systemd-container before_install: sudo apt-get install -y systemd-container
script: script:
- sudo env "PATH=$PATH" python3 -m osbuild --libdir . --output . samples/noop.json - sudo env "PATH=$PATH" python3 -m osbuild --libdir . samples/noop.json
- sudo env "PATH=$PATH" python3 -m osbuild --libdir . --output . samples/noop.json - sudo env "PATH=$PATH" python3 -m osbuild --libdir . samples/noop.json
- name: f30-boot - name: f30-boot
before_install: sudo apt-get install -y systemd-container yum qemu-kvm before_install: sudo apt-get install -y systemd-container yum qemu-kvm
script: sudo env "PATH=$PATH" python3 -m test --case f30-boot --build-pipeline samples/build-from-yum.json script: sudo env "PATH=$PATH" python3 -m test --case f30-boot --build-pipeline samples/build-from-yum.json

View file

@ -70,7 +70,7 @@ The above pipeline has no base and produces a qcow2 image.
``` ```
usage: python3 -m osbuild [-h] [--build-pipeline PIPELINE] [--store DIRECTORY] usage: python3 -m osbuild [-h] [--build-pipeline PIPELINE] [--store DIRECTORY]
[-l DIRECTORY] -o DIRECTORY [-l DIRECTORY]
PIPELINE PIPELINE
Build operating system images Build operating system images
@ -87,11 +87,6 @@ optional arguments:
-l DIRECTORY, --libdir DIRECTORY -l DIRECTORY, --libdir DIRECTORY
the directory containing stages, assemblers, and the the directory containing stages, assemblers, and the
osbuild library osbuild library
required named arguments:
-o DIRECTORY, --output DIRECTORY
provide the empty DIRECTORY as output argument to the
last stage
``` ```
### Running example ### Running example
@ -99,7 +94,7 @@ required named arguments:
You can build basic qcow2 image of Fedora 30 by running a following command: You can build basic qcow2 image of Fedora 30 by running a following command:
``` ```
sudo python3 -m osbuild -o output --libdir . samples/base-qcow2.json sudo python3 -m osbuild --libdir . samples/base-qcow2.json
``` ```
- Root rights are required because osbuild heavily relies on creating - Root rights are required because osbuild heavily relies on creating

View file

@ -23,9 +23,6 @@ def main():
help="the directory containing stages, assemblers, and the osbuild library") help="the directory containing stages, assemblers, and the osbuild library")
parser.add_argument("--json", action="store_true", parser.add_argument("--json", action="store_true",
help="output results in JSON format") help="output results in JSON format")
requiredNamed = parser.add_argument_group('required named arguments')
requiredNamed.add_argument("-o", "--output", dest="output_dir", metavar="DIRECTORY", type=os.path.abspath,
help="provide the empty DIRECTORY as output argument to the last stage", required=True)
args = parser.parse_args() args = parser.parse_args()
with open(args.pipeline_path) as f: with open(args.pipeline_path) as f:
@ -37,7 +34,7 @@ def main():
pipeline.prepend_build_pipeline(build) pipeline.prepend_build_pipeline(build)
try: try:
pipeline.run(args.output_dir, args.store, interactive=not args.json, libdir=args.libdir) pipeline.run(args.store, interactive=not args.json, libdir=args.libdir)
except KeyboardInterrupt: except KeyboardInterrupt:
print() print()
print(f"{RESET}{BOLD}{RED}Aborted{RESET}") print(f"{RESET}{BOLD}{RED}Aborted{RESET}")

View file

@ -111,7 +111,7 @@ class Assembler:
def run(self, tree, build_tree, output_dir=None, interactive=False, check=True, libdir=None): def run(self, tree, build_tree, output_dir=None, interactive=False, check=True, libdir=None):
with buildroot.BuildRoot(build_tree) as build_root: with buildroot.BuildRoot(build_tree) as build_root:
if interactive: if interactive:
print_header(f"Assembling: {self.name}", self.options) print_header(f"Assembler {self.name}: {self.id}", self.options)
args = { args = {
"tree": "/run/osbuild/tree", "tree": "/run/osbuild/tree",
@ -195,11 +195,11 @@ class Pipeline:
finally: finally:
subprocess.run(["umount", "--lazy", tmp], check=True) subprocess.run(["umount", "--lazy", tmp], check=True)
def run(self, output_dir, store, interactive=False, check=True, libdir=None): def run(self, store, interactive=False, check=True, libdir=None):
os.makedirs("/run/osbuild", exist_ok=True) os.makedirs("/run/osbuild", exist_ok=True)
object_store = objectstore.ObjectStore(store) object_store = objectstore.ObjectStore(store)
if self.build: if self.build:
if not self.build.run(None, store, interactive, check, libdir): if not self.build.run(store, interactive, check, libdir):
return False return False
with self.get_buildtree(object_store) as build_tree: with self.get_buildtree(object_store) as build_tree:
@ -228,8 +228,9 @@ class Pipeline:
libdir=libdir): libdir=libdir):
return False return False
if self.assembler: if self.assembler and not object_store.contains(self.output_id):
with object_store.get(self.tree_id) as tree: with object_store.get(self.tree_id) as tree, \
object_store.new(self.output_id) as output_dir:
if not self.assembler.run(tree, if not self.assembler.run(tree,
build_tree, build_tree,
output_dir=output_dir, output_dir=output_dir,

View file

@ -43,7 +43,6 @@ if __name__ == '__main__':
args = parser.parse_args() args = parser.parse_args()
logging.info(f"Using {OBJECTS} for objects storage.") logging.info(f"Using {OBJECTS} for objects storage.")
logging.info(f"Using {OUTPUT_DIR} for output images storage.")
logging.info(f"Using {OSBUILD} for building images.") logging.info(f"Using {OSBUILD} for building images.")
f30_boot = IntegrationTestCase( f30_boot = IntegrationTestCase(

View file

@ -1,3 +1,4 @@
import json
import logging import logging
import subprocess import subprocess
import sys import sys
@ -5,8 +6,8 @@ import sys
from .config import * from .config import *
def run_osbuild(pipeline: str, build_pipeline: str, check=True): def run_osbuild(pipeline: str, build_pipeline: str):
cmd = OSBUILD + ["--store", OBJECTS, "-o", OUTPUT_DIR, pipeline] cmd = OSBUILD + ["--json", "--store", OBJECTS, pipeline]
if build_pipeline: if build_pipeline:
cmd += ["--build-pipeline", build_pipeline] cmd += ["--build-pipeline", build_pipeline]
logging.info(f"Running osbuild: {cmd}") logging.info(f"Running osbuild: {cmd}")
@ -17,10 +18,10 @@ def run_osbuild(pipeline: str, build_pipeline: str, check=True):
print(osbuild.stderr.decode()) print(osbuild.stderr.decode())
print(f"{BOLD}STDOUT{RESET}") print(f"{BOLD}STDOUT{RESET}")
print(osbuild.stdout.decode()) print(osbuild.stdout.decode())
if check: sys.exit(1)
sys.exit(1)
return osbuild.returncode result = json.loads(osbuild.stdout.decode())
return result["tree_id"], result.get("output_id")
def build_testing_image(pipeline_full_path, build_pipeline_full_path): def build_testing_image(pipeline_full_path, build_pipeline_full_path):

View file

@ -7,5 +7,4 @@ RESET = "\033[0m"
BOLD = "\033[1m" BOLD = "\033[1m"
RED = "\033[31m" RED = "\033[31m"
OBJECTS = os.environ.get("OBJECTS", ".osbuild-test") OBJECTS = os.environ.get("OBJECTS", ".osbuild-test")
OUTPUT_DIR = os.environ.get("OUTPUT_DIR", "output-test")
OSBUILD = os.environ.get("OSBUILD", "python3 -m osbuild --libdir .").split(' ') OSBUILD = os.environ.get("OSBUILD", "python3 -m osbuild --libdir .").split(' ')

View file

@ -11,7 +11,7 @@ def run_image(file_name: str):
silence = ["-nographic", "-monitor", "none", "-serial", "none"] silence = ["-nographic", "-monitor", "none", "-serial", "none"]
serial = ["-chardev", "stdio,id=stdio", "-device", "virtio-serial", "-device", "virtserialport,chardev=stdio"] serial = ["-chardev", "stdio,id=stdio", "-device", "virtio-serial", "-device", "virtserialport,chardev=stdio"]
cmd = ["qemu-system-x86_64", "-m", "1024", "-snapshot"] + \ cmd = ["qemu-system-x86_64", "-m", "1024", "-snapshot"] + \
acceleration + silence + serial + [f"{OUTPUT_DIR}/{file_name}"] acceleration + silence + serial + [file_name]
logging.info(f"Booting image: {cmd}") logging.info(f"Booting image: {cmd}")
return subprocess.run(cmd, capture_output=True, timeout=EXPECTED_TIME_TO_BOOT, encoding="utf-8", check=True) return subprocess.run(cmd, capture_output=True, timeout=EXPECTED_TIME_TO_BOOT, encoding="utf-8", check=True)
@ -19,7 +19,7 @@ def run_image(file_name: str):
@contextlib.contextmanager @contextlib.contextmanager
def extract_image(file_name: str): def extract_image(file_name: str):
extract_dir = tempfile.mkdtemp(prefix="osbuild-") extract_dir = tempfile.mkdtemp(prefix="osbuild-")
archive = path.join(os.getcwd(), OUTPUT_DIR, file_name) archive = path.join(os.getcwd(), file_name)
subprocess.run(["tar", "xf", archive], cwd=extract_dir, check=True) subprocess.run(["tar", "xf", archive], cwd=extract_dir, check=True)
try: try:
yield extract_dir yield extract_dir

View file

@ -5,6 +5,7 @@ from typing import List, Callable, Any
from . import evaluate_test, rel_path from . import evaluate_test, rel_path
from .build import run_osbuild from .build import run_osbuild
from .run import run_image, extract_image from .run import run_image, extract_image
from test.integration_tests.config import *
class IntegrationTestType(Enum): class IntegrationTestType(Enum):
@ -22,18 +23,18 @@ class IntegrationTestCase:
type: IntegrationTestType type: IntegrationTestType
def run(self): def run(self):
run_osbuild(rel_path(f"pipelines/{self.pipeline}"), self.build_pipeline) tree_id, output_id = run_osbuild(rel_path(f"pipelines/{self.pipeline}"), self.build_pipeline)
if self.type == IntegrationTestType.BOOT_WITH_QEMU: if self.type == IntegrationTestType.BOOT_WITH_QEMU:
self.run_and_test() self.run_and_test(output_id)
else: else:
self.extract_and_test() self.extract_and_test(output_id)
def run_and_test(self): def run_and_test(self, output_id):
r = run_image(self.output_image) r = run_image(f"{OBJECTS}/refs/{output_id}/{self.output_image}")
for test in self.test_cases: for test in self.test_cases:
evaluate_test(test, r.stdout) evaluate_test(test, r.stdout)
def extract_and_test(self): def extract_and_test(self, output_id):
with extract_image(self.output_image) as fstree: with extract_image(f"{OBJECTS}/refs/{output_id}/{self.output_image}") as fstree:
for test in self.test_cases: for test in self.test_cases:
evaluate_test(lambda: test(fstree), name=test.__name__) evaluate_test(lambda: test(fstree), name=test.__name__)