Skip to content

Commit

Permalink
plugins: add support for customizations
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
gicmo committed May 2, 2022
1 parent d8c9332 commit 76c90e0
Show file tree
Hide file tree
Showing 5 changed files with 130 additions and 1 deletion.
9 changes: 8 additions & 1 deletion plugins/builder/osbuild.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,15 +168,19 @@ def __init__(self, distro: str, ireqs: List[ImageRequest], koji: Koji):
self.distribution = distro
self.image_requests = ireqs
self.koji = koji
self.customizations: Optional[dict] = None

def as_dict(self):
return {
res = {
"distribution": self.distribution,
"koji": self.koji.as_dict(),
"image_requests": [
img.as_dict() for img in self.image_requests
]
}
if self.customizations:
res["customizations"] = self.customizations
return res


class ImageStatus(enum.Enum):
Expand Down Expand Up @@ -641,6 +645,9 @@ def handler(self, name, version, distro, image_types, target, arches, opts):
kojidata = ComposeRequest.Koji(self.koji_url, self.id, nvr)
request = ComposeRequest(distro, ireqs, kojidata)

# Additional customizations are passed through
request.customizations = opts.get("customizations")

self.upload_json(request.as_dict(), "compose-request")

cid = client.compose_create(request)
Expand Down
9 changes: 9 additions & 0 deletions plugins/cli/osbuild.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
"""


import json
import os
import optparse # pylint: disable=deprecated-module
from pprint import pprint

Expand Down Expand Up @@ -46,6 +48,8 @@ def parse_args(argv):

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",
help="Don't wait on image creation")
parser.add_option("--ostree-parent", type=str, dest="ostree_parent",
Expand Down Expand Up @@ -135,6 +139,11 @@ def handle_osbuild_image(options, session, argv):
if 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
check_target(session, target)

Expand Down
4 changes: 4 additions & 0 deletions plugins/hub/osbuild.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@
"type": "object",
"additionalProperties": False,
"properties": {
"customizations": {
"type": "object",
"additionalProperties": True
},
"ostree": {
"type": "object",
"$ref": "#/definitions/ostree"
Expand Down
43 changes: 43 additions & 0 deletions test/unit/test_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -1026,6 +1026,49 @@ def test_oauth2_delay(self):
res = handler.handler(*args)
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
def test_ostree_compose(self):
# Check we properly handle ostree compose requests
Expand Down
66 changes: 66 additions & 0 deletions test/unit/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@

import contextlib
import io
import json
import os
import tempfile

import koji
import koji_cli.lib as kl
from flexmock import flexmock
Expand Down Expand Up @@ -125,6 +129,68 @@ def test_basic_invocation(self):
r = self.plugin.handle_osbuild_image(options, session, argv)
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):
# Check we properly handle ostree specific options

Expand Down

0 comments on commit 76c90e0

Please sign in to comment.