plugins: support for ostree specific options

OStree compose requests need special options, like the `ref` the
`parent` and the `url`. Add support for those options to all three
plugins:
  The command line plugin now takes `--ostree-{parent,ref,url}`
  and passes it to koji via the existing options dictionary.

  The JSON schemata in the hub plugin was adjusted to allow these
  new options.

  Finally the builder plugin will look for the new `ostree` dict
  inside the options, create an `OSTreeOptions` object from it,
  and attach it to each image request.

NB: since the ostree options are per image request and are thus
architecture dependent we support a "$arch" substition in the
`parent` and `ref` options that will be resolved by the plugin;
this allows to builds arch specific commits for with a single
compose request.

Add the respective unit tests.
This commit is contained in:
Christian Kellner 2022-02-14 10:02:11 +00:00 committed by Achilleas Koutsou
parent ce21817676
commit 78ed04dbd6
5 changed files with 189 additions and 2 deletions

View file

@ -55,6 +55,30 @@ KOJIAPI_IMAGE_TYPES = {
# cloud API. It is based on the corresponding OpenAPI specification # cloud API. It is based on the corresponding OpenAPI specification
# version '2' with integrated koji support (>= commit c81d0d0). # 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: class Repository:
def __init__(self, baseurl: str, gpgkey: str = None): def __init__(self, baseurl: str, gpgkey: str = None):
self.baseurl = baseurl self.baseurl = baseurl
@ -79,16 +103,21 @@ class ImageRequest:
self.architecture = arch self.architecture = arch
self.image_type = image_type self.image_type = image_type
self.repositories = repos self.repositories = repos
self.ostree: OSTreeOptions = None
def as_dict(self): def as_dict(self):
arch = self.architecture arch = self.architecture
return { res = {
"architecture": self.architecture, "architecture": self.architecture,
"image_type": self.image_type, "image_type": self.image_type,
"repositories": [ "repositories": [
repo.as_dict(arch) for repo in self.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: class NVR:
@ -571,6 +600,15 @@ class OSBuildImage(BaseTaskHandler):
# Arches and image types # Arches and image types
image_types = [self.map_koji_api_image_type(i) for i in 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] 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", self.logger.debug("Creating compose: %s (%s)\n koji: %s\n images: %s",
nvr, distro, self.koji_url, nvr, distro, self.koji_url,
str([i.as_dict() for i in ireqs])) str([i.as_dict() for i in ireqs]))

View file

@ -21,6 +21,12 @@ def parse_args(argv):
parser.add_option("--nowait", action="store_false", dest="wait", parser.add_option("--nowait", action="store_false", dest="wait",
help="Don't wait on image creation") 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("--release", help="Forcibly set the release field")
parser.add_option("--repo", action="append", parser.add_option("--repo", action="append",
help=("Specify a repo that will override the repo used to install " 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: if args.skip_tag:
opts["skip_tag"] = True 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 # Do some early checks to be able to give quick feedback
check_target(session, target) check_target(session, target)

View file

@ -49,11 +49,31 @@ OSBUILD_IMAGE_SCHEMA = {
"$ref": "#/definitions/options" "$ref": "#/definitions/options"
}], }],
"definitions": { "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", "title": "Optional arguments",
"type": "object", "type": "object",
"additionalProperties": False, "additionalProperties": False,
"properties": { "properties": {
"ostree": {
"type": "object",
"$ref": "#/definitions/ostree"
},
"repo": { "repo": {
"type": "array", "type": "array",
"description": "Repositories", "description": "Repositories",

View file

@ -909,3 +909,54 @@ class TestBuilderPlugin(PluginTest):
res = handler.handler(*args) res = handler.handler(*args)
assert res, "invalid compose result" 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())

View file

@ -125,6 +125,63 @@ class TestCliPlugin(PluginTest):
r = self.plugin.handle_osbuild_image(options, session, argv) r = self.plugin.handle_osbuild_image(options, session, argv)
self.assertEqual(r, 0) 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): def test_target_check(self):
# unknown build target # unknown build target
session = flexmock() session = flexmock()