diff --git a/plugins/builder/osbuild.py b/plugins/builder/osbuild.py index bec9b51..31d1755 100644 --- a/plugins/builder/osbuild.py +++ b/plugins/builder/osbuild.py @@ -55,6 +55,30 @@ KOJIAPI_IMAGE_TYPES = { # cloud API. It is based on the corresponding OpenAPI specification # version '2' with integrated koji support (>= commit c81d0d0). + +class OSTreeOptions: + def __init__(self, data) -> None: + self.parent = data.get("parent") + self.ref = data.get("ref") + self.url = data.get("url") + + def as_dict(self, arch: str = ""): + res = {} + + if self.parent: + tmp = Template(self.parent) + res["parent"] = tmp.substitute(arch=arch) + + if self.ref: + tmp = Template(self.ref) + res["ref"] = tmp.substitute(arch=arch) + + if self.url: + res["url"] = self.url + + return res + + class Repository: def __init__(self, baseurl: str, gpgkey: str = None): self.baseurl = baseurl @@ -79,16 +103,21 @@ class ImageRequest: self.architecture = arch self.image_type = image_type self.repositories = repos + self.ostree: OSTreeOptions = None def as_dict(self): arch = self.architecture - return { + res = { "architecture": self.architecture, "image_type": self.image_type, "repositories": [ repo.as_dict(arch) for repo in self.repositories ] } + if self.ostree: + res["ostree"] = self.ostree.as_dict(self.architecture) + + return res class NVR: @@ -571,6 +600,15 @@ class OSBuildImage(BaseTaskHandler): # Arches and image types image_types = [self.map_koji_api_image_type(i) for i in image_types] ireqs = [ImageRequest(a, i, repos) for a in arches for i in image_types] + + # OStree specific options + ostree = opts.get("ostree") + if ostree: + ostree = OSTreeOptions(ostree) + + for ireq in ireqs: + ireq.ostree = ostree + self.logger.debug("Creating compose: %s (%s)\n koji: %s\n images: %s", nvr, distro, self.koji_url, str([i.as_dict() for i in ireqs])) diff --git a/plugins/cli/osbuild.py b/plugins/cli/osbuild.py index 9cca763..71412fd 100755 --- a/plugins/cli/osbuild.py +++ b/plugins/cli/osbuild.py @@ -21,6 +21,12 @@ def parse_args(argv): parser.add_option("--nowait", action="store_false", dest="wait", help="Don't wait on image creation") + parser.add_option("--ostree-parent", type=str, dest="ostree_parent", + help="The OSTree commit parent for OSTree commit image types") + parser.add_option("--ostree-ref", type=str, dest="ostree_ref", + help="The OSTree commit ref for OSTree commit image types") + parser.add_option("--ostree-url", type=str, dest="ostree_url", + help="URL to the OSTree repo for OSTree commit image types") parser.add_option("--release", help="Forcibly set the release field") parser.add_option("--repo", action="append", help=("Specify a repo that will override the repo used to install " @@ -82,6 +88,21 @@ def handle_osbuild_image(options, session, argv): if args.skip_tag: opts["skip_tag"] = True + # ostree command line parameters + ostree = {} + + if args.ostree_parent: + ostree["parent"] = args.ostree_parent + + if args.ostree_ref: + ostree["ref"] = args.ostree_ref + + if args.ostree_url: + ostree["url"] = args.ostree_url + + if ostree: + opts["ostree"] = ostree + # Do some early checks to be able to give quick feedback check_target(session, target) diff --git a/plugins/hub/osbuild.py b/plugins/hub/osbuild.py index 811dbd2..de6c006 100644 --- a/plugins/hub/osbuild.py +++ b/plugins/hub/osbuild.py @@ -49,11 +49,31 @@ OSBUILD_IMAGE_SCHEMA = { "$ref": "#/definitions/options" }], "definitions": { - "options":{ + "ostree": { + "title": "OSTree specific options", + "type": "object", + "additionalProperties": False, + "properties": { + "parent": { + "type": "string" + }, + "ref": { + "type": "string" + }, + "url": { + "type": "string" + } + } + }, + "options": { "title": "Optional arguments", "type": "object", "additionalProperties": False, "properties": { + "ostree": { + "type": "object", + "$ref": "#/definitions/ostree" + }, "repo": { "type": "array", "description": "Repositories", diff --git a/test/unit/test_builder.py b/test/unit/test_builder.py index aeba73d..bc5aade 100644 --- a/test/unit/test_builder.py +++ b/test/unit/test_builder.py @@ -909,3 +909,54 @@ class TestBuilderPlugin(PluginTest): res = handler.handler(*args) assert res, "invalid compose result" + + @httpretty.activate + def test_ostree_compose(self): + # Check we properly handle ostree compose requests + session = self.mock_session() + handler = self.make_handler(session=session) + + arches = ["x86_64", "s390x"] + repos = ["http://1.repo", "https://2.repo"] + args = ["name", "version", "distro", + ["image_type"], + "fedora-candidate", + arches, + {"repo": repos, + "ostree": { + "parent": "osbuild/$arch/p", + "ref": "osbuild/$arch/r", + "url": "https://osbuild.org/repo" + }}] + + url = self.plugin.DEFAULT_COMPOSER_URL + composer = MockComposer(url, architectures=arches) + composer.httpretty_regsiter() + + res = handler.handler(*args) + assert res, "invalid compose result" + compose_id = res["composer"]["id"] + compose = composer.composes.get(compose_id) + self.assertIsNotNone(compose) + + ireqs = compose["request"]["image_requests"] + + # Check we got all the requested architectures + ireq_arches = [i["architecture"] for i in ireqs] + diff = set(arches) ^ set(ireq_arches) + self.assertEqual(diff, set()) + + for ir in ireqs: + assert "ostree" in ir + ostree = ir["ostree"] + for key in ("parent", "ref", "url"): + assert key in ostree + assert ostree["url"] == "https://osbuild.org/repo" + + ireq_parents = [i["ostree"]["parent"] for i in ireqs] + diff = set(f"osbuild/{a}/p" for a in arches) ^ set(ireq_parents) + self.assertEqual(diff, set()) + + ireq_refs = [i["ostree"]["ref"] for i in ireqs] + diff = set(f"osbuild/{a}/r" for a in arches) ^ set(ireq_refs) + self.assertEqual(diff, set()) diff --git a/test/unit/test_cli.py b/test/unit/test_cli.py index 7dcfc90..96b62a2 100644 --- a/test/unit/test_cli.py +++ b/test/unit/test_cli.py @@ -125,6 +125,63 @@ class TestCliPlugin(PluginTest): r = self.plugin.handle_osbuild_image(options, session, argv) self.assertEqual(r, 0) + def test_ostree_options(self): + # Check we properly handle ostree specific options + + argv = [ + # the required positional arguments + "name", "version", "distro", "target", "arch1", + # optional keyword arguments + "--repo", "https://first.repo", + "--repo", "https://second.repo", + "--release", "20200202.n2", + "--ostree-parent", "ostree/$arch/staging", + "--ostree-ref", "ostree/$arch/production", + "--ostree-url", "https://osbuild.org/repo", + ] + + expected_args = ["name", "version", "distro", + ['guest-image'], # the default image type + "target", + ['arch1']] + + expected_opts = { + "release": "20200202.n2", + "repo": ["https://first.repo", "https://second.repo"], + "ostree": { + "parent": "ostree/$arch/staging", + "ref": "ostree/$arch/production", + "url": "https://osbuild.org/repo", + } + } + + task_result = {"compose_id": "42", "build_id": 23} + task_id = 1 + koji_lib = self.mock_koji_lib() + + options = self.mock_options() + session = flexmock() + + self.mock_session_add_valid_tag(session) + + session.should_receive("osbuildImage") \ + .with_args(*expected_args, opts=expected_opts) \ + .and_return(task_id) \ + .once() + + session.should_receive("logout") \ + .with_args() \ + .once() + + session.should_receive("getTaskResult") \ + .with_args(task_id) \ + .and_return(task_result) \ + .once() + + setattr(self.plugin, "kl", koji_lib) + r = self.plugin.handle_osbuild_image(options, session, argv) + self.assertEqual(r, 0) + def test_target_check(self): # unknown build target session = flexmock()