07d08627c6
The files created in runroot task are owned by root by default (because that's who is running the processes to create them). Making the results world readable allows the compose to work, but it still can be difficult to clean up old composes if they contain random files owned by root. Fixes: https://pagure.io/pungi/issue/1039 JIRA: COMPOSE-2906 Signed-off-by: Lubomír Sedlář <lsedlar@redhat.com>
621 lines
24 KiB
Python
621 lines
24 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
|
|
# This program is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation; version 2 of the License.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU Library General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, see <https://gnu.org/licenses/>.
|
|
|
|
|
|
import os
|
|
import re
|
|
import time
|
|
import threading
|
|
import contextlib
|
|
|
|
import koji
|
|
from kobo.shortcuts import run
|
|
import six
|
|
from six.moves import configparser, shlex_quote
|
|
import six.moves.xmlrpc_client as xmlrpclib
|
|
|
|
from .. import util
|
|
from ..arch_utils import getBaseArch
|
|
|
|
|
|
class KojiWrapper(object):
|
|
lock = threading.Lock()
|
|
|
|
def __init__(self, profile):
|
|
self.profile = profile
|
|
with self.lock:
|
|
self.koji_module = koji.get_profile_module(profile)
|
|
session_opts = {}
|
|
for key in ('krbservice', 'timeout', 'keepalive',
|
|
'max_retries', 'retry_interval', 'anon_retry',
|
|
'offline_retry', 'offline_retry_interval',
|
|
'debug', 'debug_xmlrpc', 'krb_rdns',
|
|
'serverca',
|
|
'use_fast_upload'):
|
|
value = getattr(self.koji_module.config, key, None)
|
|
if value is not None:
|
|
session_opts[key] = value
|
|
self.koji_proxy = koji.ClientSession(self.koji_module.config.server, session_opts)
|
|
|
|
def login(self):
|
|
"""Authenticate to the hub."""
|
|
auth_type = self.koji_module.config.authtype
|
|
if auth_type == 'ssl' or (os.path.isfile(os.path.expanduser(self.koji_module.config.cert))
|
|
and auth_type is None):
|
|
self.koji_proxy.ssl_login(os.path.expanduser(self.koji_module.config.cert),
|
|
os.path.expanduser(self.koji_module.config.ca),
|
|
os.path.expanduser(self.koji_module.config.serverca))
|
|
elif auth_type == 'kerberos':
|
|
self.koji_proxy.krb_login(
|
|
getattr(self.koji_module.config, 'principal', None),
|
|
getattr(self.koji_module.config, 'keytab', None))
|
|
else:
|
|
raise RuntimeError('Unsupported authentication type in Koji')
|
|
|
|
def _get_cmd(self, *args):
|
|
return ["koji", "--profile=%s" % self.profile] + list(args)
|
|
|
|
def get_runroot_cmd(self, target, arch, command, quiet=False, use_shell=True,
|
|
channel=None, packages=None, mounts=None, weight=None,
|
|
task_id=True, new_chroot=False, destdir=None):
|
|
cmd = self._get_cmd("runroot")
|
|
|
|
if quiet:
|
|
cmd.append("--quiet")
|
|
|
|
if new_chroot:
|
|
cmd.append("--new-chroot")
|
|
|
|
if use_shell:
|
|
cmd.append("--use-shell")
|
|
|
|
if task_id:
|
|
cmd.append("--task-id")
|
|
|
|
if channel:
|
|
cmd.append("--channel-override=%s" % channel)
|
|
else:
|
|
cmd.append("--channel-override=runroot-local")
|
|
|
|
if weight:
|
|
cmd.append("--weight=%s" % int(weight))
|
|
|
|
for package in packages or []:
|
|
cmd.append("--package=%s" % package)
|
|
|
|
for mount in mounts or []:
|
|
# directories are *not* created here
|
|
cmd.append("--mount=%s" % mount)
|
|
|
|
# IMPORTANT: all --opts have to be provided *before* args
|
|
|
|
cmd.append(target)
|
|
|
|
# i686 -> i386 etc.
|
|
arch = getBaseArch(arch)
|
|
cmd.append(arch)
|
|
|
|
if isinstance(command, list):
|
|
command = " ".join([shlex_quote(i) for i in command])
|
|
|
|
# HACK: remove rpmdb and yum cache
|
|
command = "rm -f /var/lib/rpm/__db*; rm -rf /var/cache/yum/*; set -x; " + command
|
|
|
|
if destdir:
|
|
# Make the files world readable
|
|
command += " && chmod -R a+r %s" % shlex_quote(destdir)
|
|
# and owned by the same user that is running the process
|
|
command += " && chown -R %d %s" % (os.getuid(), shlex_quote(destdir))
|
|
cmd.append(command)
|
|
|
|
return cmd
|
|
|
|
@contextlib.contextmanager
|
|
def get_koji_cmd_env(self):
|
|
"""Get environment variables for running a koji command.
|
|
|
|
If we are authenticated with a keytab, we need a fresh credentials
|
|
cache to avoid possible race condition.
|
|
"""
|
|
if getattr(self.koji_module.config, 'keytab', None):
|
|
with util.temp_dir(prefix='krb_ccache') as tempdir:
|
|
env = os.environ.copy()
|
|
env['KRB5CCNAME'] = 'DIR:%s' % tempdir
|
|
yield env
|
|
else:
|
|
yield None
|
|
|
|
def run_runroot_cmd(self, command, log_file=None):
|
|
"""
|
|
Run koji runroot command and wait for results.
|
|
|
|
If the command specified --task-id, and the first line of output
|
|
contains the id, it will be captured and returned.
|
|
"""
|
|
task_id = None
|
|
with self.get_koji_cmd_env() as env:
|
|
retcode, output = run(command, can_fail=True, logfile=log_file,
|
|
show_cmd=True, env=env, universal_newlines=True)
|
|
if "--task-id" in command:
|
|
first_line = output.splitlines()[0]
|
|
if re.match(r'^\d+$', first_line):
|
|
task_id = int(first_line)
|
|
# Remove first line from the output, preserving any trailing newlines.
|
|
output_ends_with_eol = output.endswith("\n")
|
|
output = "\n".join(output.splitlines()[1:])
|
|
if output_ends_with_eol:
|
|
output += "\n"
|
|
|
|
return {
|
|
"retcode": retcode,
|
|
"output": output,
|
|
"task_id": task_id,
|
|
}
|
|
|
|
def get_image_build_cmd(self, config_options, conf_file_dest, wait=True, scratch=False):
|
|
"""
|
|
@param config_options
|
|
@param conf_file_dest - a destination in compose workdir for the conf file to be written
|
|
@param wait=True
|
|
@param scratch=False
|
|
"""
|
|
# Usage: koji image-build [options] <name> <version> <target> <install-tree-url> <arch> [<arch>...]
|
|
sub_command = "image-build"
|
|
# The minimum set of options
|
|
min_options = ("name", "version", "target", "install_tree", "arches", "format", "kickstart", "ksurl", "distro")
|
|
assert set(min_options).issubset(set(config_options['image-build'].keys())), "image-build requires at least %s got '%s'" % (", ".join(min_options), config_options)
|
|
cfg_parser = configparser.ConfigParser()
|
|
for section, opts in config_options.items():
|
|
cfg_parser.add_section(section)
|
|
for option, value in opts.items():
|
|
if isinstance(value, list):
|
|
value = ','.join(value)
|
|
if not isinstance(value, six.string_types):
|
|
# Python 3 configparser will reject non-string values.
|
|
value = str(value)
|
|
cfg_parser.set(section, option, value)
|
|
|
|
fd = open(conf_file_dest, "w")
|
|
cfg_parser.write(fd)
|
|
fd.close()
|
|
|
|
cmd = self._get_cmd(sub_command, "--config=%s" % conf_file_dest)
|
|
if wait:
|
|
cmd.append("--wait")
|
|
if scratch:
|
|
cmd.append("--scratch")
|
|
|
|
return cmd
|
|
|
|
def get_live_media_cmd(self, options, wait=True):
|
|
# Usage: koji spin-livemedia [options] <name> <version> <target> <arch> <kickstart-file>
|
|
cmd = self._get_cmd('spin-livemedia')
|
|
|
|
for key in ('name', 'version', 'target', 'arch', 'ksfile'):
|
|
if key not in options:
|
|
raise ValueError('Expected options to have key "%s"' % key)
|
|
cmd.append(options[key])
|
|
if 'install_tree' not in options:
|
|
raise ValueError('Expected options to have key "install_tree"')
|
|
cmd.append('--install-tree=%s' % options['install_tree'])
|
|
|
|
for repo in options.get('repo', []):
|
|
cmd.append('--repo=%s' % repo)
|
|
|
|
if options.get('scratch'):
|
|
cmd.append('--scratch')
|
|
|
|
if options.get('skip_tag'):
|
|
cmd.append('--skip-tag')
|
|
|
|
if 'ksurl' in options:
|
|
cmd.append('--ksurl=%s' % options['ksurl'])
|
|
|
|
if 'release' in options:
|
|
cmd.append('--release=%s' % options['release'])
|
|
|
|
if 'can_fail' in options:
|
|
cmd.append('--can-fail=%s' % ','.join(options['can_fail']))
|
|
|
|
if wait:
|
|
cmd.append('--wait')
|
|
|
|
return cmd
|
|
|
|
def get_create_image_cmd(self, name, version, target, arch, ks_file, repos,
|
|
image_type="live", image_format=None, release=None,
|
|
wait=True, archive=False, specfile=None, ksurl=None):
|
|
# Usage: koji spin-livecd [options] <name> <version> <target> <arch> <kickstart-file>
|
|
# Usage: koji spin-appliance [options] <name> <version> <target> <arch> <kickstart-file>
|
|
# Examples:
|
|
# * name: RHEL-7.0
|
|
# * name: Satellite-6.0.1-RHEL-6
|
|
# ** -<type>.<arch>
|
|
# * version: YYYYMMDD[.n|.t].X
|
|
# * release: 1
|
|
|
|
cmd = self._get_cmd()
|
|
|
|
if image_type == "live":
|
|
cmd.append("spin-livecd")
|
|
elif image_type == "appliance":
|
|
cmd.append("spin-appliance")
|
|
else:
|
|
raise ValueError("Invalid image type: %s" % image_type)
|
|
|
|
if not archive:
|
|
cmd.append("--scratch")
|
|
|
|
cmd.append("--noprogress")
|
|
|
|
if wait:
|
|
cmd.append("--wait")
|
|
else:
|
|
cmd.append("--nowait")
|
|
|
|
if specfile:
|
|
cmd.append("--specfile=%s" % specfile)
|
|
|
|
if ksurl:
|
|
cmd.append("--ksurl=%s" % ksurl)
|
|
|
|
if isinstance(repos, list):
|
|
for repo in repos:
|
|
cmd.append("--repo=%s" % repo)
|
|
else:
|
|
cmd.append("--repo=%s" % repos)
|
|
|
|
if image_format:
|
|
if image_type != "appliance":
|
|
raise ValueError("Format can be specified only for appliance images'")
|
|
supported_formats = ["raw", "qcow", "qcow2", "vmx"]
|
|
if image_format not in supported_formats:
|
|
raise ValueError("Format is not supported: %s. Supported formats: %s" % (image_format, " ".join(sorted(supported_formats))))
|
|
cmd.append("--format=%s" % image_format)
|
|
|
|
if release is not None:
|
|
cmd.append("--release=%s" % release)
|
|
|
|
# IMPORTANT: all --opts have to be provided *before* args
|
|
# Usage: koji spin-livecd [options] <name> <version> <target> <arch> <kickstart-file>
|
|
|
|
cmd.append(name)
|
|
cmd.append(version)
|
|
cmd.append(target)
|
|
|
|
# i686 -> i386 etc.
|
|
arch = getBaseArch(arch)
|
|
cmd.append(arch)
|
|
|
|
cmd.append(ks_file)
|
|
|
|
return cmd
|
|
|
|
def _has_connection_error(self, output):
|
|
"""Checks if output indicates connection error."""
|
|
return re.search('error: failed to connect\n$', output)
|
|
|
|
def _wait_for_task(self, task_id, logfile=None, max_retries=None):
|
|
"""Tries to wait for a task to finish. On connection error it will
|
|
retry with `watch-task` command.
|
|
"""
|
|
cmd = self._get_cmd('watch-task', str(task_id))
|
|
attempt = 0
|
|
|
|
while True:
|
|
retcode, output = run(cmd, can_fail=True, logfile=logfile, universal_newlines=True)
|
|
|
|
if retcode == 0 or not self._has_connection_error(output):
|
|
# Task finished for reason other than connection error.
|
|
return retcode, output
|
|
|
|
attempt += 1
|
|
if max_retries and attempt >= max_retries:
|
|
break
|
|
time.sleep(attempt * 10)
|
|
|
|
raise RuntimeError('Failed to wait for task %s. Too many connection errors.' % task_id)
|
|
|
|
def run_blocking_cmd(self, command, log_file=None, max_retries=None):
|
|
"""
|
|
Run a blocking koji command. Returns a dict with output of the command,
|
|
its exit code and parsed task id. This method will block until the
|
|
command finishes.
|
|
"""
|
|
with self.get_koji_cmd_env() as env:
|
|
retcode, output = run(command, can_fail=True, logfile=log_file,
|
|
env=env, universal_newlines=True)
|
|
|
|
match = re.search(r"Created task: (\d+)", output)
|
|
if not match:
|
|
raise RuntimeError("Could not find task ID in output. Command '%s' returned '%s'."
|
|
% (" ".join(command), output))
|
|
task_id = int(match.groups()[0])
|
|
|
|
if retcode != 0 and self._has_connection_error(output):
|
|
retcode, output = self._wait_for_task(task_id, logfile=log_file, max_retries=max_retries)
|
|
|
|
return {
|
|
"retcode": retcode,
|
|
"output": output,
|
|
"task_id": task_id,
|
|
}
|
|
|
|
def watch_task(self, task_id, log_file=None, max_retries=None):
|
|
retcode, _ = self._wait_for_task(task_id, logfile=log_file, max_retries=max_retries)
|
|
return retcode
|
|
|
|
def get_image_paths(self, task_id, callback=None):
|
|
"""
|
|
Given an image task in Koji, get a mapping from arches to a list of
|
|
paths to results of the task.
|
|
|
|
If callback is given, it will be called once with arch of every failed
|
|
subtask.
|
|
"""
|
|
result = {}
|
|
|
|
# task = self.koji_proxy.getTaskInfo(task_id, request=True)
|
|
children_tasks = self.koji_proxy.getTaskChildren(task_id, request=True)
|
|
|
|
for child_task in children_tasks:
|
|
if child_task['method'] not in ['createImage', 'createLiveMedia', 'createAppliance']:
|
|
continue
|
|
|
|
if child_task['state'] != koji.TASK_STATES['CLOSED']:
|
|
# The subtask is failed, which can happen with the can_fail
|
|
# option. If given, call the callback, and go to next child.
|
|
if callback:
|
|
callback(child_task['arch'])
|
|
continue
|
|
|
|
is_scratch = child_task['request'][-1].get('scratch', False)
|
|
task_result = self.koji_proxy.getTaskResult(child_task['id'])
|
|
|
|
if is_scratch:
|
|
topdir = os.path.join(
|
|
self.koji_module.pathinfo.work(),
|
|
self.koji_module.pathinfo.taskrelpath(child_task['id'])
|
|
)
|
|
else:
|
|
build = self.koji_proxy.getImageBuild("%(name)s-%(version)s-%(release)s" % task_result)
|
|
build["name"] = task_result["name"]
|
|
build["version"] = task_result["version"]
|
|
build["release"] = task_result["release"]
|
|
build["arch"] = task_result["arch"]
|
|
topdir = self.koji_module.pathinfo.imagebuild(build)
|
|
|
|
for i in task_result["files"]:
|
|
result.setdefault(task_result['arch'], []).append(os.path.join(topdir, i))
|
|
|
|
return result
|
|
|
|
def get_image_path(self, task_id):
|
|
result = []
|
|
task_info_list = []
|
|
task_info_list.append(self.koji_proxy.getTaskInfo(task_id, request=True))
|
|
task_info_list.extend(self.koji_proxy.getTaskChildren(task_id, request=True))
|
|
|
|
# scan parent and child tasks for certain methods
|
|
task_info = None
|
|
for i in task_info_list:
|
|
if i["method"] in ("createAppliance", "createLiveCD", 'createImage'):
|
|
task_info = i
|
|
break
|
|
|
|
scratch = task_info["request"][-1].get("scratch", False)
|
|
task_result = self.koji_proxy.getTaskResult(task_info["id"])
|
|
task_result.pop("rpmlist", None)
|
|
|
|
if scratch:
|
|
topdir = os.path.join(self.koji_module.pathinfo.work(), self.koji_module.pathinfo.taskrelpath(task_info["id"]))
|
|
else:
|
|
build = self.koji_proxy.getImageBuild("%(name)s-%(version)s-%(release)s" % task_result)
|
|
build["name"] = task_result["name"]
|
|
build["version"] = task_result["version"]
|
|
build["release"] = task_result["release"]
|
|
build["arch"] = task_result["arch"]
|
|
topdir = self.koji_module.pathinfo.imagebuild(build)
|
|
for i in task_result["files"]:
|
|
result.append(os.path.join(topdir, i))
|
|
return result
|
|
|
|
def get_wrapped_rpm_path(self, task_id, srpm=False):
|
|
result = []
|
|
parent_task = self.koji_proxy.getTaskInfo(task_id, request=True)
|
|
task_info_list = []
|
|
task_info_list.extend(self.koji_proxy.getTaskChildren(task_id, request=True))
|
|
|
|
# scan parent and child tasks for certain methods
|
|
task_info = None
|
|
for i in task_info_list:
|
|
if i["method"] in ("wrapperRPM"):
|
|
task_info = i
|
|
break
|
|
|
|
# Check parent_task if it's scratch build
|
|
scratch = parent_task["request"][-1].get("scratch", False)
|
|
|
|
# Get results of wrapperRPM task
|
|
# {'buildroot_id': 2479520,
|
|
# 'logs': ['checkout.log', 'root.log', 'state.log', 'build.log'],
|
|
# 'rpms': ['foreman-discovery-image-2.1.0-2.el7sat.noarch.rpm'],
|
|
# 'srpm': 'foreman-discovery-image-2.1.0-2.el7sat.src.rpm'}
|
|
task_result = self.koji_proxy.getTaskResult(task_info["id"])
|
|
|
|
# Get koji dir with results (rpms, srpms, logs, ...)
|
|
topdir = os.path.join(self.koji_module.pathinfo.work(), self.koji_module.pathinfo.taskrelpath(task_info["id"]))
|
|
|
|
# TODO: Maybe use different approach for non-scratch builds - see get_image_path()
|
|
|
|
# Get list of filenames that should be returned
|
|
result_files = task_result["rpms"]
|
|
if srpm:
|
|
result_files += [task_result["srpm"]]
|
|
|
|
# Prepare list with paths to the required files
|
|
for i in result_files:
|
|
result.append(os.path.join(topdir, i))
|
|
|
|
return result
|
|
|
|
def get_signed_wrapped_rpms_paths(self, task_id, sigkey, srpm=False):
|
|
result = []
|
|
parent_task = self.koji_proxy.getTaskInfo(task_id, request=True)
|
|
task_info_list = []
|
|
task_info_list.extend(self.koji_proxy.getTaskChildren(task_id, request=True))
|
|
|
|
# scan parent and child tasks for certain methods
|
|
task_info = None
|
|
for i in task_info_list:
|
|
if i["method"] in ("wrapperRPM"):
|
|
task_info = i
|
|
break
|
|
|
|
# Check parent_task if it's scratch build
|
|
scratch = parent_task["request"][-1].get("scratch", False)
|
|
if scratch:
|
|
raise RuntimeError("Scratch builds cannot be signed!")
|
|
|
|
# Get results of wrapperRPM task
|
|
# {'buildroot_id': 2479520,
|
|
# 'logs': ['checkout.log', 'root.log', 'state.log', 'build.log'],
|
|
# 'rpms': ['foreman-discovery-image-2.1.0-2.el7sat.noarch.rpm'],
|
|
# 'srpm': 'foreman-discovery-image-2.1.0-2.el7sat.src.rpm'}
|
|
task_result = self.koji_proxy.getTaskResult(task_info["id"])
|
|
|
|
# Get list of filenames that should be returned
|
|
result_files = task_result["rpms"]
|
|
if srpm:
|
|
result_files += [task_result["srpm"]]
|
|
|
|
# Prepare list with paths to the required files
|
|
for i in result_files:
|
|
rpminfo = self.koji_proxy.getRPM(i)
|
|
build = self.koji_proxy.getBuild(rpminfo["build_id"])
|
|
path = os.path.join(self.koji_module.pathinfo.build(build), self.koji_module.pathinfo.signed(rpminfo, sigkey))
|
|
result.append(path)
|
|
|
|
return result
|
|
|
|
def get_build_nvrs(self, task_id):
|
|
builds = self.koji_proxy.listBuilds(taskID=task_id)
|
|
return [build.get("nvr") for build in builds if build.get("nvr")]
|
|
|
|
def multicall_map(self, koji_session, koji_session_fnc, list_of_args=None, list_of_kwargs=None):
|
|
"""
|
|
Calls the `koji_session_fnc` using Koji multicall feature N times based on the list of
|
|
arguments passed in `list_of_args` and `list_of_kwargs`.
|
|
Returns list of responses sorted the same way as input args/kwargs. In case of error,
|
|
the error message is logged and None is returned.
|
|
|
|
For example to get the package ids of "httpd" and "apr" packages:
|
|
ids = multicall_map(session, session.getPackageID, ["httpd", "apr"])
|
|
# ids is now [280, 632]
|
|
|
|
:param KojiSessions koji_session: KojiSession to use for multicall.
|
|
:param object koji_session_fnc: Python object representing the KojiSession method to call.
|
|
:param list list_of_args: List of args which are passed to each call of koji_session_fnc.
|
|
:param list list_of_kwargs: List of kwargs which are passed to each call of koji_session_fnc.
|
|
"""
|
|
if list_of_args is None and list_of_kwargs is None:
|
|
raise ValueError("One of list_of_args or list_of_kwargs must be set.")
|
|
|
|
if (type(list_of_args) not in [type(None), list] or
|
|
type(list_of_kwargs) not in [type(None), list]):
|
|
raise ValueError("list_of_args and list_of_kwargs must be list or None.")
|
|
|
|
if list_of_kwargs is None:
|
|
list_of_kwargs = [{}] * len(list_of_args)
|
|
if list_of_args is None:
|
|
list_of_args = [[]] * len(list_of_kwargs)
|
|
|
|
if len(list_of_args) != len(list_of_kwargs):
|
|
raise ValueError("Length of list_of_args and list_of_kwargs must be the same.")
|
|
|
|
koji_session.multicall = True
|
|
for args, kwargs in zip(list_of_args, list_of_kwargs):
|
|
if type(args) != list:
|
|
args = [args]
|
|
if type(kwargs) != dict:
|
|
raise ValueError("Every item in list_of_kwargs must be a dict")
|
|
koji_session_fnc(*args, **kwargs)
|
|
|
|
responses = koji_session.multiCall(strict=True)
|
|
|
|
if not responses:
|
|
return None
|
|
if type(responses) != list:
|
|
raise ValueError(
|
|
"Fault element was returned for multicall of method %r: %r" % (
|
|
koji_session_fnc, responses))
|
|
|
|
results = []
|
|
|
|
# For the response specification, see
|
|
# https://web.archive.org/web/20060624230303/http://www.xmlrpc.com/discuss/msgReader$1208?mode=topic
|
|
# Relevant part of this:
|
|
# Multicall returns an array of responses. There will be one response for each call in
|
|
# the original array. The result will either be a one-item array containing the result value,
|
|
# or a struct of the form found inside the standard <fault> element.
|
|
for response, args, kwargs in zip(responses, list_of_args, list_of_kwargs):
|
|
if type(response) == list:
|
|
if not response:
|
|
raise ValueError(
|
|
"Empty list returned for multicall of method %r with args %r, %r" % (
|
|
koji_session_fnc, args, kwargs))
|
|
results.append(response[0])
|
|
else:
|
|
raise ValueError(
|
|
"Unexpected data returned for multicall of method %r with args %r, %r: %r" % (
|
|
koji_session_fnc, args, kwargs, response))
|
|
|
|
return results
|
|
|
|
|
|
@util.retry(wait_on=(xmlrpclib.ProtocolError, koji.GenericError))
|
|
def retrying_multicall_map(self, *args, **kwargs):
|
|
"""
|
|
Retrying version of multicall_map. This tries to retry the Koji call
|
|
in case of koji.GenericError or xmlrpclib.ProtocolError.
|
|
|
|
Please refer to koji_multicall_map for further specification of arguments.
|
|
"""
|
|
return self.multicall_map(*args, **kwargs)
|
|
|
|
|
|
|
|
def get_buildroot_rpms(compose, task_id):
|
|
"""Get build root RPMs - either from runroot or local"""
|
|
result = []
|
|
if task_id:
|
|
# runroot
|
|
koji = KojiWrapper(compose.conf['koji_profile'])
|
|
buildroot_infos = koji.koji_proxy.listBuildroots(taskID=task_id)
|
|
buildroot_info = buildroot_infos[-1]
|
|
data = koji.koji_proxy.listRPMs(componentBuildrootID=buildroot_info["id"])
|
|
for rpm_info in data:
|
|
fmt = "%(nvr)s.%(arch)s"
|
|
result.append(fmt % rpm_info)
|
|
else:
|
|
# local
|
|
retcode, output = run("rpm -qa --qf='%{name}-%{version}-%{release}.%{arch}\n'",
|
|
universal_newlines=True)
|
|
for i in output.splitlines():
|
|
if not i:
|
|
continue
|
|
result.append(i)
|
|
return sorted(result)
|