plugins: add support for customizations
The Cloud API supports passing in a variety of image customizations, like e.g. extra packages or pre-defining users. Add a new command line option to the client `--customizations` which takes a path to a JSON file which contains the customziations; they will be passed via the existing `opts` argument to the hub. Add support for `customizations` to the `opts`/`options` arguments to the hub plugin. No validation to the object is done. Instead we rely in Composer for the validation of the content. Add support for `customizations` the image `ComposeRequest` in the builder plugin. All specified values are just passed through to composer as-is. Add tests for the respective plugins.
This commit is contained in:
parent
d8c9332257
commit
591a55aad5
5 changed files with 130 additions and 1 deletions
|
|
@ -168,15 +168,19 @@ class ComposeRequest:
|
||||||
self.distribution = distro
|
self.distribution = distro
|
||||||
self.image_requests = ireqs
|
self.image_requests = ireqs
|
||||||
self.koji = koji
|
self.koji = koji
|
||||||
|
self.customizations: Optional[dict] = None
|
||||||
|
|
||||||
def as_dict(self):
|
def as_dict(self):
|
||||||
return {
|
res = {
|
||||||
"distribution": self.distribution,
|
"distribution": self.distribution,
|
||||||
"koji": self.koji.as_dict(),
|
"koji": self.koji.as_dict(),
|
||||||
"image_requests": [
|
"image_requests": [
|
||||||
img.as_dict() for img in self.image_requests
|
img.as_dict() for img in self.image_requests
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
if self.customizations:
|
||||||
|
res["customizations"] = self.customizations
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
class ImageStatus(enum.Enum):
|
class ImageStatus(enum.Enum):
|
||||||
|
|
@ -641,6 +645,9 @@ class OSBuildImage(BaseTaskHandler):
|
||||||
kojidata = ComposeRequest.Koji(self.koji_url, self.id, nvr)
|
kojidata = ComposeRequest.Koji(self.koji_url, self.id, nvr)
|
||||||
request = ComposeRequest(distro, ireqs, kojidata)
|
request = ComposeRequest(distro, ireqs, kojidata)
|
||||||
|
|
||||||
|
# Additional customizations are passed through
|
||||||
|
request.customizations = opts.get("customizations")
|
||||||
|
|
||||||
self.upload_json(request.as_dict(), "compose-request")
|
self.upload_json(request.as_dict(), "compose-request")
|
||||||
|
|
||||||
cid = client.compose_create(request)
|
cid = client.compose_create(request)
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ is provided by the koji osbuild plugin for the koji hub.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
import json
|
||||||
import optparse # pylint: disable=deprecated-module
|
import optparse # pylint: disable=deprecated-module
|
||||||
from pprint import pprint
|
from pprint import pprint
|
||||||
|
|
||||||
|
|
@ -46,6 +47,8 @@ def parse_args(argv):
|
||||||
|
|
||||||
parser = kl.OptionParser(usage=kl.get_usage_str(usage))
|
parser = kl.OptionParser(usage=kl.get_usage_str(usage))
|
||||||
|
|
||||||
|
parser.add_option("--customizations", type=str, default=None, dest="customizations",
|
||||||
|
help="Additional customizations to pass to Composer (json file)")
|
||||||
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",
|
parser.add_option("--ostree-parent", type=str, dest="ostree_parent",
|
||||||
|
|
@ -98,6 +101,7 @@ def check_target(session, name):
|
||||||
target['dest_tag_name'])
|
target['dest_tag_name'])
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable=too-many-branches
|
||||||
@export_cli
|
@export_cli
|
||||||
def handle_osbuild_image(options, session, argv):
|
def handle_osbuild_image(options, session, argv):
|
||||||
"[build] Build images via osbuild"
|
"[build] Build images via osbuild"
|
||||||
|
|
@ -135,6 +139,11 @@ def handle_osbuild_image(options, session, argv):
|
||||||
if ostree:
|
if ostree:
|
||||||
opts["ostree"] = ostree
|
opts["ostree"] = ostree
|
||||||
|
|
||||||
|
# customizations handling
|
||||||
|
if args.customizations:
|
||||||
|
with open(args.customizations, "r", encoding="utf-8") as f:
|
||||||
|
opts["customizations"] = json.load(f)
|
||||||
|
|
||||||
# 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)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -87,6 +87,10 @@ OSBUILD_IMAGE_SCHEMA = {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"additionalProperties": False,
|
"additionalProperties": False,
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"customizations": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": True
|
||||||
|
},
|
||||||
"ostree": {
|
"ostree": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"$ref": "#/definitions/ostree"
|
"$ref": "#/definitions/ostree"
|
||||||
|
|
|
||||||
|
|
@ -1026,6 +1026,49 @@ class TestBuilderPlugin(PluginTest): # pylint: disable=too-many-public-methods
|
||||||
res = handler.handler(*args)
|
res = handler.handler(*args)
|
||||||
assert res, "invalid compose result"
|
assert res, "invalid compose result"
|
||||||
|
|
||||||
|
@httpretty.activate
|
||||||
|
def test_customizations_compose(self):
|
||||||
|
# Check we properly handle compose requests with customizations
|
||||||
|
session = self.mock_session()
|
||||||
|
handler = self.make_handler(session=session)
|
||||||
|
|
||||||
|
customizations = {
|
||||||
|
"packages": [
|
||||||
|
"emacs"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
arches = ["x86_64", "s390x"]
|
||||||
|
repos = ["http://1.repo", "https://2.repo"]
|
||||||
|
args = ["name", "version", "distro",
|
||||||
|
["image_type"],
|
||||||
|
"fedora-candidate",
|
||||||
|
arches,
|
||||||
|
{"repo": repos,
|
||||||
|
"customizations": customizations
|
||||||
|
}]
|
||||||
|
|
||||||
|
url = self.plugin.DEFAULT_COMPOSER_URL
|
||||||
|
composer = MockComposer(url, architectures=arches)
|
||||||
|
composer.httpretty_register()
|
||||||
|
|
||||||
|
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())
|
||||||
|
|
||||||
|
# Check we actually got the customizations
|
||||||
|
self.assertEqual(compose["request"].get("customizations"),
|
||||||
|
customizations)
|
||||||
|
|
||||||
@httpretty.activate
|
@httpretty.activate
|
||||||
def test_ostree_compose(self):
|
def test_ostree_compose(self):
|
||||||
# Check we properly handle ostree compose requests
|
# Check we properly handle ostree compose requests
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,10 @@
|
||||||
|
|
||||||
import contextlib
|
import contextlib
|
||||||
import io
|
import io
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import tempfile
|
||||||
|
|
||||||
import koji
|
import koji
|
||||||
import koji_cli.lib as kl
|
import koji_cli.lib as kl
|
||||||
from flexmock import flexmock
|
from flexmock import flexmock
|
||||||
|
|
@ -125,6 +129,68 @@ 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_customizations_options(self):
|
||||||
|
with tempfile.TemporaryDirectory() as tmpdir:
|
||||||
|
|
||||||
|
customizations = {
|
||||||
|
"packages": [
|
||||||
|
"emacs"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
path = os.path.join(tmpdir, "customizations.json")
|
||||||
|
|
||||||
|
with open(path, "w", encoding="utf-8") as f:
|
||||||
|
json.dump(customizations, f)
|
||||||
|
|
||||||
|
argv = [
|
||||||
|
# the required positional arguments
|
||||||
|
"name", "version", "distro", "target", "arch1",
|
||||||
|
# optional keyword arguments
|
||||||
|
"--repo", "https://first.repo",
|
||||||
|
"--repo", "https://second.repo",
|
||||||
|
"--release", "20200202.n2",
|
||||||
|
"--customizations", path
|
||||||
|
]
|
||||||
|
|
||||||
|
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"],
|
||||||
|
"customizations": customizations
|
||||||
|
}
|
||||||
|
|
||||||
|
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_ostree_options(self):
|
def test_ostree_options(self):
|
||||||
# Check we properly handle ostree specific options
|
# Check we properly handle ostree specific options
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue