The upload options are expected to be provided as a JSON file. The same options will be used for all image type and architecture combinations, similarly as it is done for ostree options. Extend unit tests to cover the newly added functionality.
182 lines
6.4 KiB
Python
Executable file
182 lines
6.4 KiB
Python
Executable file
"""Koji osbuild integration - koji client plugin
|
|
|
|
This koji plugin provides a new 'osbuild-image' command for the koji
|
|
command line tool. It uses the 'osbuildImage' XMLRPC endpoint, that
|
|
is provided by the koji osbuild plugin for the koji hub.
|
|
"""
|
|
|
|
|
|
import json
|
|
import optparse # pylint: disable=deprecated-module
|
|
from pprint import pprint
|
|
|
|
import koji
|
|
import koji_cli.lib as kl
|
|
from koji.plugin import export_cli
|
|
|
|
|
|
def parse_repo(_option, _opt, value, parser):
|
|
repo = parser.values.repo
|
|
if repo and isinstance(repo[0], dict):
|
|
repo.append({"baseurl": value})
|
|
return
|
|
|
|
if not repo:
|
|
parser.values.repo = repo = []
|
|
repo.append(value)
|
|
|
|
|
|
def parse_repo_package_set(_option, opt, value, parser):
|
|
if not parser.values.repo:
|
|
raise optparse.OptionValueError(f"Need '--repo' for {opt}")
|
|
|
|
repo = parser.values.repo.pop()
|
|
if not isinstance(repo, dict):
|
|
repo = {
|
|
"baseurl": repo
|
|
}
|
|
ps = repo.get("package_sets", [])
|
|
vals = set(map(lambda x: x.strip(), value.split(";")))
|
|
repo["package_sets"] = list(sorted(set(ps).union(vals)))
|
|
parser.values.repo.append(repo)
|
|
|
|
|
|
def parse_args(argv):
|
|
usage = ("usage: %prog osbuild-image [options] <name> <version> "
|
|
"<distro> <target> <arch> [<arch> ...]")
|
|
|
|
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("--upload-options", type=str, default=None, dest="upload_options",
|
|
help="Cloud target upload options (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",
|
|
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="callback", callback=parse_repo, nargs=1, type=str,
|
|
help=("Specify a repo that will override the repo used to install "
|
|
"RPMs in the image. May be used multiple times. The "
|
|
"build tag repo associated with the target is the default."))
|
|
parser.add_option("--repo-package-sets", dest="repo", nargs=1, type=str,
|
|
action="callback", callback=parse_repo_package_set,
|
|
help=("Specify the package sets for the last repository. "
|
|
"Individual set items are separated by ';'. "
|
|
"Maybe be used multiple times"))
|
|
parser.add_option("--image-type", metavar="TYPE",
|
|
help='Request an image-type [default: guest-image]',
|
|
type=str, default="guest-image")
|
|
parser.add_option("--skip-tag", action="store_true",
|
|
help="Do not attempt to tag package")
|
|
parser.add_option("--wait", action="store_true",
|
|
help="Wait on the image creation, even if running in the background")
|
|
|
|
opts, args = parser.parse_args(argv)
|
|
if len(args) < 5:
|
|
parser.error("At least five arguments are required: a name, "
|
|
"a version, a distribution, a build target, "
|
|
"and 1 or more architectures.")
|
|
|
|
for i, arg in enumerate(("name", "version", "distro", "target")):
|
|
setattr(opts, arg, args[i])
|
|
setattr(opts, "arch", args[4:])
|
|
|
|
return opts
|
|
|
|
|
|
def check_target(session, name):
|
|
"""Check the target with name exists and has a destination tag"""
|
|
|
|
target = session.getBuildTarget(name)
|
|
if not target:
|
|
raise koji.GenericError(f"Unknown build target: {name}")
|
|
|
|
tag = session.getTag(target['dest_tag'])
|
|
if not tag:
|
|
raise koji.GenericError(f"Unknown destination tag: {target['dest_tag_name']}")
|
|
|
|
|
|
# pylint: disable=too-many-branches
|
|
@export_cli
|
|
def handle_osbuild_image(options, session, argv):
|
|
"[build] Build images via osbuild"
|
|
args = parse_args(argv)
|
|
|
|
name, version, arch, target = args.name, args.version, args.arch, args.target
|
|
distro, image_type = args.distro, args.image_type
|
|
|
|
opts = {}
|
|
|
|
if args.release:
|
|
opts["release"] = args.release
|
|
|
|
if args.repo:
|
|
opts["repo"] = args.repo
|
|
|
|
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
|
|
|
|
# customizations handling
|
|
if args.customizations:
|
|
with open(args.customizations, "r", encoding="utf-8") as f:
|
|
opts["customizations"] = json.load(f)
|
|
|
|
# cloud upload options handling
|
|
if args.upload_options:
|
|
with open(args.upload_options, "r", encoding="utf-8") as f:
|
|
opts["upload_options"] = json.load(f)
|
|
|
|
# Do some early checks to be able to give quick feedback
|
|
check_target(session, target)
|
|
|
|
if not options.quiet:
|
|
print("name:", name)
|
|
print("version:", version)
|
|
print("distro:", distro)
|
|
print("arches:", ", ".join(arch))
|
|
print("target:", target)
|
|
print("image type:", image_type)
|
|
pprint(opts)
|
|
|
|
kl.activate_session(session, options)
|
|
|
|
task_id = session.osbuildImage(name, version, distro, image_type, target, arch, opts=opts)
|
|
|
|
if not options.quiet:
|
|
print(f"Created task: {task_id}")
|
|
print(f"Task info: {options.weburl}/taskinfo?taskID={task_id}")
|
|
|
|
# pylint: disable=protected-access
|
|
if (args.wait is None and kl._running_in_bg()) or args.wait is False:
|
|
# either running in the background or must not wait by user's
|
|
# request. All done.
|
|
return None
|
|
|
|
session.logout()
|
|
res = kl.watch_tasks(session, [task_id], quiet=options.quiet)
|
|
|
|
if res == 0:
|
|
result = session.getTaskResult(task_id)
|
|
print(result)
|
|
return res
|