draft builds

This commit is contained in:
Yuming Zhu 2023-09-22 21:57:30 +08:00 committed by Yu Ming Zhu
parent 124450cec0
commit 87409499a3
30 changed files with 1763 additions and 186 deletions

View file

@ -1102,6 +1102,8 @@ class BuildTask(BaseTaskHandler):
% (data['name'], target_info['dest_tag_name']))
# TODO - more pre tests
archlist = self.getArchList(build_tag, h, extra=extra_arches)
# pass draft option in
data['draft'] = opts.get('draft')
# let the system know about the build we're attempting
if not self.opts.get('scratch'):
# scratch builds do not get imported
@ -2176,6 +2178,8 @@ class WrapperRPMTask(BaseBuildTask):
data['extra'] = {'source': {'original_url': source['url']}}
if opts.get('custom_user_metadata'):
data['extra']['custom_user_metadata'] = opts['custom_user_metadata']
# pass draft option in
data['draft'] = opts.get('draft')
self.logger.info("Reading package config for %(name)s" % data)
pkg_cfg = self.session.getPackageConfig(build_target['dest_tag'], data['name'])
if not opts.get('skip_tag'):
@ -5249,6 +5253,7 @@ Subject: %(nvr)s %(result)s %(operation)s by %(user_name)s\r
To: %(to_addrs)s\r
X-Koji-Package: %(pkg_name)s\r
X-Koji-NVR: %(nvr)s\r
X-Koji-Draft: %(draft)s\r
X-Koji-User: %(user_name)s\r
X-Koji-Status: %(status)s\r
%(tag_headers)s\r
@ -5278,6 +5283,7 @@ Status: %(status)s\r
user = self.session.getUser(user_info)
pkg_name = build['package_name']
nvr = koji.buildLabel(build)
draft = build.get('draft', False)
user_name = user['name']
from_addr = self.options.from_addr
@ -5349,6 +5355,7 @@ X-Koji-Tag: %(dest_tag)s\r
X-Koji-Package: %(build_pkg_name)s\r
X-Koji-Builder: %(build_owner)s\r
X-Koji-Status: %(status)s\r
X-Koji-Draft: %(draft)s\r
\r
Package: %(build_nvr)s\r
Tag: %(dest_tag)s\r
@ -5448,6 +5455,7 @@ Build Info: %(weburl)s/buildinfo?buildID=%(build_id)i\r
build_nvr = koji.buildLabel(build)
build_id = build['id']
build_owner = build['owner_name']
draft = build.get('draft', False)
# target comes from session.py:_get_build_target()
dest_tag = None
if target is not None:

View file

@ -580,6 +580,8 @@ def handle_build(options, session, args):
parser.add_option("--custom-user-metadata", type="str",
help="Provide a JSON string of custom metadata to be deserialized and "
"stored under the build's extra.custom_user_metadata field")
parser.add_option("--draft", action="store_true",
help="Build draft build instead")
(build_opts, args) = parser.parse_args(args)
if len(args) != 2:
parser.error("Exactly two arguments (a build target and a SCM URL or srpm file) are "
@ -588,6 +590,8 @@ def handle_build(options, session, args):
parser.error("--no-/rebuild-srpm is only allowed for --scratch builds")
if build_opts.arch_override and not build_opts.scratch:
parser.error("--arch_override is only allowed for --scratch builds")
if build_opts.scratch and build_opts.draft:
parser.error("--scratch and --draft cannot be both specfied")
custom_user_metadata = {}
if build_opts.custom_user_metadata:
try:
@ -618,7 +622,7 @@ def handle_build(options, session, args):
if build_opts.arch_override:
opts['arch_override'] = koji.parse_arches(build_opts.arch_override)
for key in ('skip_tag', 'scratch', 'repo_id', 'fail_fast', 'wait_repo', 'wait_builds',
'rebuild_srpm'):
'rebuild_srpm', 'draft'):
val = getattr(build_opts, key)
if val is not None:
opts[key] = val
@ -830,6 +834,8 @@ def handle_wrapper_rpm(options, session, args):
parser.add_option("--nowait", action="store_false", dest="wait", help="Don't wait on build")
parser.add_option("--background", action="store_true",
help="Run the build at a lower priority")
parser.add_option("--draft", action="store_true",
help="Build draft build instead")
(build_opts, args) = parser.parse_args(args)
if build_opts.inis:
@ -839,6 +845,8 @@ def handle_wrapper_rpm(options, session, args):
if len(args) < 3:
parser.error("You must provide a build target, a build ID or NVR, "
"and a SCM URL to a specfile fragment")
if build_opts.scratch and build_opts.draft:
parser.error("--scratch and --draft cannot be both specfied")
activate_session(session, options)
target = args[0]
@ -874,6 +882,8 @@ def handle_wrapper_rpm(options, session, args):
opts['skip_tag'] = True
if build_opts.scratch:
opts['scratch'] = True
if build_opts.draft:
opts['draft'] = True
task_id = session.wrapperRPM(build_id, url, target, priority, opts=opts)
print("Created task: %d" % task_id)
print("Task info: %s/taskinfo?taskID=%s" % (options.weburl, task_id))
@ -1314,6 +1324,10 @@ def handle_import(goptions, session, args):
parser.add_option("--test", action="store_true", help="Don't actually import")
parser.add_option("--create-build", action="store_true", help="Auto-create builds as needed")
parser.add_option("--src-epoch", help="When auto-creating builds, use this epoch")
parser.add_option("--create-draft", action="store_true",
help="Auto-create draft builds instead as needed")
parser.add_option("--draft-build", metavar='NVR|ID',
help="The target draft build to import to")
(options, args) = parser.parse_args(args)
if len(args) < 1:
parser.error("At least one package must be specified")
@ -1324,6 +1338,35 @@ def handle_import(goptions, session, args):
options.src_epoch = int(options.src_epoch)
except (ValueError, TypeError):
parser.error("Invalid value for epoch: %s" % options.src_epoch)
if options.create_draft:
print("Will create draft build instead if desired nvr doesn't exist")
options.create_build = True
draft_build = None
draft_target_nvr = None
if options.draft_build:
if options.create_build:
parser.error(
"To ensure no misuse, don't specify --draft-build while auto-creating."
)
try:
draft_build = int(options.draft_build)
except ValueError:
draft_build = options.draft_build
draft_build = session.getBuild(draft_build)
if not draft_build:
error("No such build: %s" % options.draft_build)
if not draft_build.get('draft'):
error("%s is not a draft build" % draft_build['nvr'])
b_state = koji.BUILD_STATES[draft_build['state']]
if b_state != 'COMPLETE':
error("draft build %s is expected as COMPLETE, got %s" % (draft_build['nvr'], b_state))
target_release = draft_build.get('extra', {}).get('draft', {}).get('target_release')
if not target_release:
error("Invalid draft build: %s,"
" no draft.target_release found in extra" % draft_build['nvr'])
draft_target_nvr = "%s-%s-%s" % (
draft_build['name'], draft_build['version'], target_release
)
activate_session(session, goptions)
to_import = {}
for path in args:
@ -1335,6 +1378,7 @@ def handle_import(goptions, session, args):
else:
nvr = "%(name)s-%(version)s-%(release)s" % koji.parse_NVRA(data['sourcerpm'])
to_import.setdefault(nvr, []).append((path, data))
builds_missing = False
nvrs = sorted(to_list(to_import.keys()))
for nvr in nvrs:
@ -1343,29 +1387,41 @@ def handle_import(goptions, session, args):
if data['sourcepackage']:
break
else:
if nvr == draft_target_nvr:
continue
# when no srpm and create_draft
elif options.create_draft:
print("Missing srpm for draft build creating with target nvr: %s" % nvr)
builds_missing = True
continue
# no srpm included, check for build
binfo = session.getBuild(nvr)
if not binfo:
print("Missing build or srpm: %s" % nvr)
builds_missing = True
if builds_missing and not options.create_build:
if builds_missing and (not options.create_build or options.create_draft):
print("Aborting import")
return
# local function to help us out below
def do_import(path, data):
def do_import(path, data, draft_build=None):
draft = bool(draft_build) or options.create_draft
rinfo = dict([(k, data[k]) for k in ('name', 'version', 'release', 'arch')])
prev = session.getRPM(rinfo)
if prev and not prev.get('external_repo_id', 0):
if prev['payloadhash'] == koji.hex_string(data['sigmd5']):
print("RPM already imported: %s" % path)
else:
warn("md5sum mismatch for %s" % path)
warn(" A different rpm with the same name has already been imported")
warn(" Existing sigmd5 is %r, your import has %r" % (
prev['payloadhash'], koji.hex_string(data['sigmd5'])))
print("Skipping import")
return
if draft_build or not options.create_draft:
opts = {}
if draft_build:
opts['build'] = draft_build
prev = session.getRPM(rinfo, **opts)
if prev and not prev.get('external_repo_id', 0):
if prev['payloadhash'] == koji.hex_string(data['sigmd5']):
print("RPM already imported: %s" % path)
else:
warn("md5sum mismatch for %s" % path)
warn(" A different rpm with the same name has already been imported")
warn(" Existing sigmd5 is %r, your import has %r" % (
prev['payloadhash'], koji.hex_string(data['sigmd5'])))
print("Skipping import")
return
if options.test:
print("Test mode -- skipping import for %s" % path)
return
@ -1381,41 +1437,69 @@ def handle_import(goptions, session, args):
sys.stdout.write("importing %s... " % path)
sys.stdout.flush()
try:
session.importRPM(serverdir, os.path.basename(path))
opts = {}
if draft_build:
opts['build'] = draft_build
if draft:
opts['draft'] = draft
rpminfo = session.importRPM(serverdir, os.path.basename(path), **opts)
except koji.GenericError as e:
rpminfo = None
print("\nError importing: %s" % str(e).splitlines()[-1])
sys.stdout.flush()
else:
print("done")
sys.stdout.flush()
return rpminfo
for nvr in nvrs:
# check for existing build
need_build = True
binfo = session.getBuild(nvr)
if binfo:
b_state = koji.BUILD_STATES[binfo['state']]
if b_state == 'COMPLETE':
need_build = False
elif b_state in ['FAILED', 'CANCELED']:
if not options.create_build:
print("Build %s state is %s. Skipping import" % (nvr, b_state))
is_draft = True
if nvr == draft_target_nvr:
binfo = draft_build
need_build = False
elif options.create_draft:
binfo = None
need_build = True
else:
is_draft = False
binfo = session.getBuild(nvr)
if binfo:
b_state = koji.BUILD_STATES[binfo['state']]
if b_state == 'COMPLETE':
need_build = False
elif b_state in ['FAILED', 'CANCELED']:
if not options.create_build:
print("Build %s state is %s. Skipping import" % (nvr, b_state))
continue
else:
print("Build %s exists with state=%s. Skipping import" % (nvr, b_state))
continue
else:
print("Build %s exists with state=%s. Skipping import" % (nvr, b_state))
continue
# import srpms first, if any
for path, data in to_import[nvr]:
if data['sourcepackage']:
if binfo and b_state != 'COMPLETE':
# we can not fix state for draft build by createEmptyBuild
if not is_draft and binfo and b_state != 'COMPLETE':
# need to fix the state
print("Creating empty build: %s" % nvr)
b_data = koji.util.dslice(binfo, ['name', 'version', 'release'])
b_data['epoch'] = data['epoch']
session.createEmptyBuild(**b_data)
binfo = session.getBuild(nvr)
do_import(path, data)
dbld = binfo if is_draft else None
will_create = False
if options.create_draft and not dbld:
will_create = True
print("Will create draft build with target nvr: %s while importing" % nvr)
rpminfo = do_import(path, data, draft_build=dbld)
# only needed for draft build, but
# TODO: should be able to apply to regular import
if rpminfo and rpminfo.get('build', {}).get('draft'):
binfo = rpminfo['build']
if will_create:
print("Draft build: %s created" % binfo['nvr'])
need_build = False
if need_build:
@ -1424,11 +1508,11 @@ def handle_import(goptions, session, args):
if binfo:
# should have caught this earlier, but just in case...
b_state = koji.BUILD_STATES[binfo['state']]
print("Build %s state is %s. Skipping import" % (nvr, b_state))
print("Build %s state is %s. Skipping import" % (binfo['nvr'], b_state))
continue
else:
print("No such build: %s (include matching srpm or use "
"--create-build option to add it)" % nvr)
"--create-build/--create-draft option to add it)" % nvr)
continue
else:
# let's make a new build
@ -1439,17 +1523,22 @@ def handle_import(goptions, session, args):
# pull epoch from first rpm
data = to_import[nvr][0][1]
b_data['epoch'] = data['epoch']
if options.test:
print("Test mode -- would have created empty build: %s" % nvr)
if options.create_draft:
b_data['draft'] = True
b_display = "empty draft build with target nvr: %s" % nvr
else:
print("Creating empty build: %s" % nvr)
session.createEmptyBuild(**b_data)
binfo = session.getBuild(nvr)
b_display = "empty build: %s" % nvr
if options.test:
print("Test mode -- would have created %s" % b_display)
else:
print("Creating %s" % b_display)
buildid = session.createEmptyBuild(**b_data)
binfo = session.getBuild(buildid)
for path, data in to_import[nvr]:
if data['sourcepackage']:
continue
do_import(path, data)
do_import(path, data, draft_build=binfo if is_draft else None)
def handle_import_cg(goptions, session, args):
@ -2727,11 +2816,15 @@ def anon_handle_list_tagged(goptions, session, args):
parser.add_option("--ts", type='int', metavar="TIMESTAMP",
help="query at last event before timestamp")
parser.add_option("--repo", type='int', metavar="REPO#", help="query at event for a repo")
parser.add_option("--draft-only", action="store_true", help="Only list draft builds/rpms")
parser.add_option("--no-draft", action="store_true", help="Only list regular builds/rpms")
(options, args) = parser.parse_args(args)
if len(args) == 0:
parser.error("A tag name must be specified")
elif len(args) > 2:
parser.error("Only one package name may be specified")
if options.no_draft and options.draft_only:
parser.error("--draft-only conflicts with --no-draft")
ensure_connection(session, goptions)
pathinfo = koji.PathInfo()
package = None
@ -2753,6 +2846,10 @@ def anon_handle_list_tagged(goptions, session, args):
options.rpms = True
if options.type:
opts['type'] = options.type
elif options.no_draft:
opts['draft'] = koji.FLAG_REGULAR_BUILD
elif options.draft_only:
opts['draft'] = koji.FLAG_DRAFT_BUILD
event = koji.util.eventFromOpts(session, options)
event_id = None
if event:
@ -2798,7 +2895,9 @@ def anon_handle_list_tagged(goptions, session, args):
fmt = "%(path)s"
data = [x for x in data if 'path' in x]
else:
fmt = "%(name)s-%(version)s-%(release)s.%(arch)s"
fmt = "%(name)s-%(version)s-%(release)s.%(arch)s%(draft_suffix)s"
for x in data:
x['draft_suffix'] = (' (#draft_%s)' % x['build_id']) if x.get('draft') else ''
if options.sigs:
fmt = "%(sigkey)s " + fmt
else:
@ -2861,10 +2960,13 @@ def anon_handle_list_buildroot(goptions, session, args):
fmt = "%(nvr)s.%(arch)s"
order = sorted([(fmt % x, x) for x in list_rpms])
for nvra, rinfo in order:
if options.verbose and rinfo.get('is_update'):
print("%s [update]" % nvra)
else:
print(nvra)
line = nvra
if options.verbose:
if rinfo.get('draft'):
line += " (#draft_%s)" % rinfo['build_id']
if rinfo.get('is_update'):
line += " [update]"
print(line)
list_archives = session.listArchives(**opts)
if list_archives:
@ -3403,6 +3505,8 @@ def anon_handle_list_builds(goptions, session, args):
parser.add_option("--source", help="Only builds where the source field matches (glob pattern)")
parser.add_option("--owner", help="List builds built by this owner")
parser.add_option("--volume", help="List builds by volume ID")
parser.add_option("--draft-only", action="store_true", help="Only list draft builds")
parser.add_option("--no-draft", action="store_true", help="Only list regular builds")
parser.add_option("-k", "--sort-key", action="append", metavar='FIELD',
default=[], help="Sort the list by the named field. Allowed sort keys: "
"build_id, owner_name, state")
@ -3419,6 +3523,12 @@ def anon_handle_list_builds(goptions, session, args):
value = getattr(options, key)
if value is not None:
opts[key] = value
if options.no_draft and options.draft_only:
parser.error("--draft-only conflits with --no-draft")
elif options.no_draft:
opts['draft'] = koji.FLAG_REGULAR_BUILD
elif options.draft_only:
opts['draft'] = koji.FLAG_DRAFT_BUILD
if options.cg:
opts['cgID'] = options.cg
if options.package:
@ -3520,13 +3630,24 @@ def anon_handle_rpminfo(goptions, session, args):
parser = OptionParser(usage=get_usage_str(usage))
parser.add_option("--buildroots", action="store_true",
help="show buildroots the rpm was used in")
parser.add_option("--build", metavar="NVR|ID",
help="show the rpm(s) in the build")
(options, args) = parser.parse_args(args)
if len(args) < 1:
parser.error("Please specify an RPM")
opts = {}
build = options.build
if options.build:
try:
build = int(build)
except ValueError:
pass
opts['build'] = build
ensure_connection(session, goptions)
error_hit = False
for rpm in args:
info = session.getRPM(rpm)
info = session.getRPM(rpm, **opts)
if info is None:
warn("No such rpm: %s\n" % rpm)
error_hit = True
@ -3536,24 +3657,29 @@ def anon_handle_rpminfo(goptions, session, args):
else:
info['epoch'] = str(info['epoch']) + ":"
if not info.get('external_repo_id', 0):
buildinfo = session.getBuild(info['build_id'])
buildinfo['name'] = buildinfo['package_name']
buildinfo['arch'] = 'src'
if buildinfo['epoch'] is None:
buildinfo['epoch'] = ""
if info['arch'] == 'src':
srpminfo = info.copy()
else:
buildinfo['epoch'] = str(buildinfo['epoch']) + ":"
srpminfo = session.listRPMs(buildID=info['build_id'], arches='src')[0]
if srpminfo['epoch'] is None:
srpminfo['epoch'] = ""
else:
srpminfo['epoch'] = str(srpminfo['epoch']) + ":"
buildinfo = session.getBuild(info['build_id'])
print("RPM: %(epoch)s%(name)s-%(version)s-%(release)s.%(arch)s [%(id)d]" % info)
if info.get('draft'):
print("Draft: YES")
if info.get('external_repo_id'):
repo = session.getExternalRepo(info['external_repo_id'])
print("External Repository: %(name)s [%(id)i]" % repo)
print("External Repository url: %(url)s" % repo)
else:
print("Build: %(nvr)s [%(id)d]" % buildinfo)
print("RPM Path: %s" %
os.path.join(koji.pathinfo.build(buildinfo), koji.pathinfo.rpm(info)))
print("SRPM: %(epoch)s%(name)s-%(version)s-%(release)s [%(id)d]" % buildinfo)
print("SRPM: %(epoch)s%(name)s-%(version)s-%(release)s [%(id)d]" % srpminfo)
print("SRPM Path: %s" %
os.path.join(koji.pathinfo.build(buildinfo), koji.pathinfo.rpm(buildinfo)))
os.path.join(koji.pathinfo.build(buildinfo), koji.pathinfo.rpm(srpminfo)))
print("Built: %s" % time.strftime('%a, %d %b %Y %H:%M:%S %Z',
time.localtime(info['buildtime'])))
print("SIGMD5: %(payloadhash)s" % info)
@ -3563,6 +3689,7 @@ def anon_handle_rpminfo(goptions, session, args):
headers=["license"])
if 'license' in headers:
print("License: %(license)s" % headers)
# kept for backward compatibility
print("Build ID: %(build_id)s" % info)
if info['buildroot_id'] is None:
print("No buildroot data available")
@ -3619,6 +3746,8 @@ def anon_handle_buildinfo(goptions, session, args):
info['arch'] = 'src'
info['state'] = koji.BUILD_STATES[info['state']]
print("BUILD: %(name)s-%(version)s-%(release)s [%(id)d]" % info)
if info.get('draft'):
print("Draft: YES")
print("State: %(state)s" % info)
if info['state'] == 'BUILDING':
print("Reserved by: %(cg_name)s" % info)

View file

@ -925,3 +925,10 @@ class DatetimeJSONEncoder(json.JSONEncoder):
if isinstance(o, xmlrpc_client.DateTime):
return str(o)
return json.JSONEncoder.default(self, o)
def yesno(x):
if x:
return 'Y'
else:
return 'N'

View file

@ -27,6 +27,7 @@ from __future__ import absolute_import, division
import base64
import datetime
import errno
import functools
import hashlib
import json
import logging
@ -274,6 +275,7 @@ TAG_UPDATE_TYPES = Enum((
'VOLUME_CHANGE',
'IMPORT',
'MANUAL',
'DRAFT_PROMOTION',
))
# BEGIN kojikamid dup #
@ -295,6 +297,44 @@ PRIO_DEFAULT = 20
DEFAULT_REQUEST_TIMEOUT = 60 * 60 * 12
DEFAULT_AUTH_TIMEOUT = 60
# draft release format
DRAFT_RELEASE_FORMAT = '{release}#draft_{id}'
FLAG_DRAFT_BUILD = 1
FLAG_REGULAR_BUILD = 2
FLAG_ALL_BUILD = FLAG_DRAFT_BUILD | FLAG_REGULAR_BUILD
if six.PY3:
from enum import IntFlag
# draft build bit FLAGs
class DRAFT_FLAG(IntFlag):
DRAFT = FLAG_DRAFT_BUILD
REGULAR = FLAG_REGULAR_BUILD
ALL = FLAG_ALL_BUILD
@classmethod
def _missing_(cls, value):
if not isinstance(value, int):
raise ValueError("%r is not a valid %s" % (value, cls.__name__))
# diable member creation and negative integer
return cls._value2member_map_.get(value)
def convert_draft_option(func=None, kw='draft'):
def wrapper(func):
@functools.wraps(func)
def convert(*args, **kwargs):
if kw in kwargs:
kwargs[kw] = DRAFT_FLAG(kwargs[kw])
return func(*args, **kwargs)
return convert
if func is None:
return wrapper
else:
return wrapper(func)
# BEGIN kojikamid dup #
# Exceptions
@ -2363,6 +2403,39 @@ class PathInfo(object):
return self.volumedir(build.get('volume_name')) + \
("/packages/%(name)s/%(version)s/%(release)s" % build)
def to_buildinfo(self, path):
"""Revert build dir path (<topdir>/[<volumedir>/]packages/<name>/<version>/<release>)
to build map with the data below:
- name
- version
- release
- volume_name
"""
path = path.rstrip('/')
parts = path.rsplit('/', 4)
if len(parts) != 5:
raise GenericError("Invalid build path: %s" % path)
if parts[-4] != 'packages':
raise GenericError("Invalid build path: %s, packages subdir not found" % path)
nvr = parts[-3:]
rest = parts[0]
if not rest.startswith(self.topdir):
raise GenericError("Invalid build path: %s, topdir is not %r" % (path, self.topdir))
vol_part = rest[len(self.topdir):]
if not vol_part:
vol = 'DEFAULT'
elif vol_part.startswith('/vol/'):
vol = vol_part[5:]
else:
raise GenericError(
"Invalid build path: %s, volume dir: %s is incorrect" % (path, vol_part)
)
return {'name': nvr[0],
'version': nvr[1],
'release': nvr[2],
'volume_name': vol}
def mavenbuild(self, build):
"""Return the directory where the Maven build exists in the global store
(/mnt/koji/packages)"""
@ -2410,6 +2483,27 @@ class PathInfo(object):
"""Return the path (relative to build_dir) where an rpm belongs"""
return "%(arch)s/%(name)s-%(version)s-%(release)s.%(arch)s.rpm" % rpminfo
def to_rpminfo(self, path, full=False):
"""Revert rpm path in build dir to rpm nvra map with/without build dir"""
if path.endswith('/'):
raise GenericError("Invalid path: %s, cannot be a directory" % path)
parts = path.rsplit('/', 2)
if full:
if len(parts) != 3:
raise GenericError("No build dir in path: %s" % path)
return self.to_buildinfo(parts[-3]), self._to_rpminfo(parts[-2:])
return self._to_rpminfo(parts)
def _to_rpminfo(self, parts):
if len(parts) != 2 or not parts[0]:
raise GenericError("Invalid path: %s" % '/'.join(parts))
rpminfo = parse_NVRA(parts[-1])
if parts[0] != rpminfo['arch']:
raise GenericError(
'mismatch between arch dir (%s) and arch (%s) in rpm' % (parts[0], rpminfo)
)
return rpminfo
def signed(self, rpminfo, sigkey):
"""Return the path (relative to build dir) where a signed rpm lives"""
return "data/signed/%s/" % sigkey + self.rpm(rpminfo)

View file

@ -60,6 +60,8 @@ callbacks = {
'postRepoInit': [],
'preRepoDone': [],
'postRepoDone': [],
'preBuildPromote': [],
'postBuildPromote': [],
'preCommit': [],
'postCommit': [],
# builder

File diff suppressed because it is too large Load diff

View file

@ -610,6 +610,11 @@ _default_policies = {
'priority': '''
all :: stay
''',
'draft_promotion': '''
has_perm draft-promoter :: allow
is_build_owner :: allow
all :: deny Only draft-promoter and build owner can do this via default policy
'''
}

View file

@ -320,6 +320,22 @@ def prep_repo_done(cbtype, *args, **kws):
queue_msg(address, props, kws)
@convert_datetime
@callback('postBuildPromote')
def prep_build_promote(cbtype, *args, **kws):
kws['build'] = _strip_extra(kws['build'])
address = 'build.promote'
props = {'type': cbtype[4:],
'build_id': kws['build']['id'],
'name': kws['build']['name'],
'version': kws['build']['version'],
'release': kws['build']['release'],
'draft_release': kws['draft_release'],
'target_release': kws['target_release'],
'user': kws['user']['name']}
queue_msg(address, props, kws)
def _send_msgs(urls, msgs, CONFIG):
random.shuffle(urls)
for url in urls:

View file

@ -2,6 +2,7 @@
-- from version 1.33 to 1.34
BEGIN;
-- scheduler tables
CREATE TABLE scheduler_task_runs (
id SERIAL NOT NULL PRIMARY KEY,
@ -48,4 +49,27 @@ BEGIN;
) WITHOUT OIDS;
INSERT INTO locks(name) VALUES('scheduler');
-- draft builds
INSERT INTO permissions (name, description) VALUES ('draft-promoter', 'The permission required in the default "draft_promotion" hub policy rule to promote draft build.');
ALTER TABLE build ADD COLUMN draft BOOLEAN NOT NULL DEFAULT 'false';
ALTER TABLE build ADD CONSTRAINT draft_for_rpminfo UNIQUE (id, draft);
ALTER TABLE build ADD CONSTRAINT draft_release_sane CHECK
((draft AND release ~ ('^.*#draft_' || id::TEXT || '$'))
OR NOT draft);
ALTER TABLE rpminfo ADD COLUMN draft BOOLEAN;
ALTER TABLE rpminfo DROP CONSTRAINT rpminfo_build_id_fkey;
ALTER TABLE rpminfo ADD CONSTRAINT rpminfo_build_id_draft_fkey
FOREIGN KEY (build_id, draft) REFERENCES build(id, draft)
ON UPDATE CASCADE;
ALTER TABLE rpminfo DROP CONSTRAINT rpminfo_unique_nvra;
ALTER TABLE rpminfo ADD CONSTRAINT build_id_draft_external_repo_id_sane
CHECK ((draft IS NULL AND build_id IS NULL AND external_repo_id <> 0)
OR (draft IS NOT NULL AND build_id IS NOT NULL AND external_repo_id = 0));
CREATE UNIQUE INDEX rpminfo_unique_nvra_not_draft
ON rpminfo(name,version,release,arch,external_repo_id)
WHERE draft IS NOT TRUE;
COMMIT;

View file

@ -66,6 +66,7 @@ INSERT INTO permissions (name, description) VALUES ('tag', 'Manage packages in t
INSERT INTO permissions (name, description) VALUES ('target', 'Add, edit, and remove targets.');
INSERT INTO permissions (name, description) VALUES ('win-admin', 'The default hub policy rule for "vm" requires this permission to trigger Windows builds.');
INSERT INTO permissions (name, description) VALUES ('win-import', 'Import win archives.');
INSERT INTO permissions (name, description) VALUES ('draft-promoter', 'The permission required in the default "draft_promotion" hub policy rule to promote draft build.');
CREATE TABLE user_perms (
user_id INTEGER NOT NULL REFERENCES users(id),
@ -279,11 +280,12 @@ CREATE TABLE content_generator (
-- null, or may point to a deleted task.
CREATE TABLE build (
id SERIAL NOT NULL PRIMARY KEY,
volume_id INTEGER NOT NULL REFERENCES volume (id),
volume_id INTEGER NOT NULL REFERENCES volume (id),
pkg_id INTEGER NOT NULL REFERENCES package (id) DEFERRABLE,
version TEXT NOT NULL,
release TEXT NOT NULL,
epoch INTEGER,
draft BOOLEAN NOT NULL DEFAULT 'false',
source TEXT,
create_event INTEGER NOT NULL REFERENCES events(id) DEFAULT get_event(),
start_time TIMESTAMPTZ,
@ -294,8 +296,11 @@ CREATE TABLE build (
cg_id INTEGER REFERENCES content_generator(id),
extra TEXT,
CONSTRAINT build_pkg_ver_rel UNIQUE (pkg_id, version, release),
CONSTRAINT draft_for_rpminfo UNIQUE (id, draft),
CONSTRAINT completion_sane CHECK ((state = 0 AND completion_time IS NULL) OR
(state != 0 AND completion_time IS NOT NULL))
(state <> 0 AND completion_time IS NOT NULL)),
CONSTRAINT draft_release_sane CHECK ((draft AND release ~ ('^.*#draft_' || id::TEXT || '$')) OR
NOT draft)
) WITHOUT OIDS;
CREATE INDEX build_by_pkg_id ON build (pkg_id);
@ -721,22 +726,28 @@ CREATE TABLE group_package_listing (
-- we don't store filename b/c filename should be N-V-R.A.rpm
CREATE TABLE rpminfo (
id SERIAL NOT NULL PRIMARY KEY,
build_id INTEGER REFERENCES build (id),
build_id INTEGER,
buildroot_id INTEGER REFERENCES buildroot (id),
name TEXT NOT NULL,
version TEXT NOT NULL,
release TEXT NOT NULL,
epoch INTEGER,
arch VARCHAR(16) NOT NULL,
draft BOOLEAN,
external_repo_id INTEGER NOT NULL REFERENCES external_repo(id),
payloadhash TEXT NOT NULL,
size BIGINT NOT NULL,
buildtime BIGINT NOT NULL,
metadata_only BOOLEAN NOT NULL DEFAULT FALSE,
extra TEXT,
CONSTRAINT rpminfo_unique_nvra UNIQUE (name,version,release,arch,external_repo_id)
FOREIGN KEY (build_id, draft) REFERENCES build (id, draft) ON UPDATE CASCADE,
CONSTRAINT build_id_draft_external_repo_id_sane CHECK (
(draft IS NULL AND build_id IS NULL AND external_repo_id <> 0)
OR (draft IS NOT NULL AND build_id IS NOT NULL AND external_repo_id = 0))
) WITHOUT OIDS;
CREATE INDEX rpminfo_build ON rpminfo(build_id);
CREATE UNIQUE INDEX rpminfo_unique_nvra_not_draft ON rpminfo(name,version,release,arch,external_repo_id)
WHERE draft IS NOT TRUE;
-- index for default search method for rpms, PG11+ can benefit from new include method
DO $$
DECLARE version integer;

View file

@ -5,6 +5,7 @@ X-Koji-Tag: f23
X-Koji-Package: sisu
X-Koji-Builder: user
X-Koji-Status: complete
X-Koji-Draft: False
Package: sisu-0.3.0-0.2.M1.fc23
Tag: f23

View file

@ -22,7 +22,8 @@
"completion_ts": 1424271457.10787,
"id": 612609,
"volume_name": "DEFAULT",
"nvr": "sisu-0.3.0-0.2.M1.fc23"
"nvr": "sisu-0.3.0-0.2.M1.fc23",
"draft": false
},
"target": {
"dest_tag": 292,

View file

@ -176,6 +176,7 @@ Options:
Provide a JSON string of custom metadata to be
deserialized and stored under the build's
extra.custom_user_metadata field
--draft Build draft build instead
""" % (self.progname, self.progname))
# Finally, assert that things were called as we expected.

View file

@ -69,6 +69,37 @@ Volume: DEFAULT
Task: 8 build (target, src)
Finished: Thu, 04 Mar 2021 14:45:40 UTC
Tags:
"""
anon_handle_buildinfo(self.options, self.session, [build])
self.assert_console_message(stdout, expected_stdout)
self.session.listTags.assert_called_once_with(build)
self.session.getBuild.assert_called_once_with(build)
self.session.getTaskInfo.assert_called_once_with(self.buildinfo['task_id'], request=True)
self.session.getMavenBuild.assert_called_once_with(self.buildinfo['id'])
self.session.getWinBuild.assert_called_once_with(self.buildinfo['id'])
self.session.listRPMs.assert_called_once_with(buildID=self.buildinfo['id'])
self.assertEqual(self.session.listArchives.call_count, 4)
@mock.patch('sys.stdout', new_callable=StringIO)
def test_buildinfo_draft(self, stdout):
build = 'test-build-1-1'
binfo = copy.deepcopy(self.buildinfo)
binfo['draft'] = True
self.session.getBuild.return_value = binfo
self.session.getTaskInfo.return_value = self.taskinfo
self.session.listTags.return_value = []
self.session.getMavenBuild.return_value = None
self.session.getWinBuild.return_value = None
self.session.listArchives.return_value = []
self.session.listRPMs.return_value = []
expected_stdout = """BUILD: test-build-1-1 [1]
Draft: YES
State: COMPLETE
Built by: kojiadmin
Volume: DEFAULT
Task: 8 build (target, src)
Finished: Thu, 04 Mar 2021 14:45:40 UTC
Tags:
"""
anon_handle_buildinfo(self.options, self.session, [build])
self.assert_console_message(stdout, expected_stdout)

View file

@ -87,15 +87,22 @@ class TestImport(utils.CliTestCase):
def __do_import_test(self, options, session, arguments, **kwargs):
expected = kwargs.get('expected', None)
rpm_header = kwargs.get('rpm_header', {})
rpm_headers = kwargs.get('rpm_headers', [])
if not rpm_headers:
rpm_headers = [rpm_header]
fake_srv_path = kwargs.get('srv_path', '/path/to/server/import')
upload_rpm_mock = kwargs.get('upload_rpm_mock', session.uploadWrapper)
getrpm_called = kwargs.get('getrpm_called', True)
getrpm_calls = kwargs.get('getrpm_calls', [])
import_opts = kwargs.get('import_opts', {})
import_rpm_calls = kwargs.get('import_rpm_calls', None)
with mock.patch('koji.get_header_fields') as get_header_fields_mock:
with mock.patch('koji_cli.commands.unique_path') as unique_path_mock:
with mock.patch('koji_cli.commands.activate_session') as activate_session_mock:
with mock.patch('sys.stdout', new_callable=six.StringIO) as stdout:
with upload_rpm_mock:
get_header_fields_mock.return_value = rpm_header
get_header_fields_mock.side_effect = rpm_headers
unique_path_mock.return_value = fake_srv_path
handle_import(options, session, arguments)
@ -104,21 +111,33 @@ class TestImport(utils.CliTestCase):
# check mock calls
activate_session_mock.assert_called_with(session, options)
get_header_fields_mock.assert_called_with(
arguments[0],
('name', 'version', 'release', 'epoch',
'arch', 'sigmd5', 'sourcepackage', 'sourcerpm')
)
get_header_fields_calls = [
mock.call(arguments[i],
('name', 'version', 'release', 'epoch',
'arch', 'sigmd5', 'sourcepackage', 'sourcerpm')
) for i in range(len(rpm_headers) - 1)
]
session.getRPM.assert_called_with(
dict((k, rpm_header.get(k, ''))
for k in ['release', 'version', 'arch', 'name'])
)
get_header_fields_mock.assert_has_calls(get_header_fields_calls)
if getrpm_calls:
session.getRPM.assert_has_calls(getrpm_calls)
elif getrpm_called:
session.getRPM.assert_called_with(
dict((k, rpm_header.get(k, ''))
for k in ['release', 'version', 'arch', 'name'])
)
else:
session.getRPM.assert_not_called()
unique_path_mock.assert_called_with('cli-import')
upload_rpm_mock.assert_called_with(arguments[0], self.fake_srv_dir)
session.importRPM.assert_called_with(
self.fake_srv_dir, os.path.basename(arguments[0]))
if import_rpm_calls:
session.importRPM.assert_has_calls(import_rpm_calls)
else:
session.importRPM.assert_called_with(
self.fake_srv_dir, os.path.basename(arguments[0]), **import_opts)
# reset for next test
activate_session_mock.reset_mock()
@ -136,6 +155,7 @@ class TestImport(utils.CliTestCase):
expected = kwargs.get('expected', None)
expected_warn = kwargs.get('expected_warn', None)
rpm_header = kwargs.get('rpm_header', {})
getrpm_called = kwargs.get('getrpm_called', True)
with mock.patch('koji.get_header_fields') as get_header_fields_mock:
get_header_fields_mock.return_value = rpm_header
@ -152,11 +172,13 @@ class TestImport(utils.CliTestCase):
('name', 'version', 'release', 'epoch',
'arch', 'sigmd5', 'sourcepackage', 'sourcerpm')
)
session.getRPM.assert_called_with(
dict((k, rpm_header.get(k, ''))
for k in ['release', 'version', 'arch', 'name'])
)
if getrpm_called:
session.getRPM.assert_called_with(
dict((k, rpm_header.get(k, ''))
for k in ['release', 'version', 'arch', 'name'])
)
else:
session.getRPM.assert_not_called()
session.uploadWrapper.assert_not_called()
session.importRPM.assert_not_called()
@ -677,6 +699,225 @@ class TestImport(utils.CliTestCase):
activate_session=None)
activate_session_mock.assert_not_called()
@mock.patch('sys.stdout', new_callable=six.StringIO)
@mock.patch('sys.stderr', new_callable=six.StringIO)
@mock.patch('koji_cli.commands.activate_session')
def test_handle_import_src_rpm_with_create_draft(
self,
activate_session_mock,
stderr,
stdout):
"""Test handle_import SRPM import with creating draft build case."""
arguments = ['/path/to/bash-4.4.12-5.fc26.rpm', '--create-draft']
options = mock.MagicMock()
session = mock.MagicMock()
session.importRPM.return_value = {'build': {'nvr': 'a-draft-build', 'draft': True}}
nvr = '%(name)s-%(version)s-%(release)s' % self.srpm_header
# Case 1. import src rpm with --create-draft
# result: success
expected = "Will create draft build instead if desired nvr doesn't exist\n"
expected += "Will create draft build with target nvr: %s while importing\n" % nvr
expected += "uploading %s... done\n" % arguments[0]
expected += "importing %s... done\n" % arguments[0]
expected += "Draft build: a-draft-build created\n"
self.__do_import_test(
options, session, arguments,
rpm_header=self.srpm_header,
expected=expected,
getrpm_called=False,
import_opts={'draft': True})
@mock.patch('sys.stdout', new_callable=six.StringIO)
@mock.patch('sys.stderr', new_callable=six.StringIO)
@mock.patch('koji_cli.commands.activate_session')
def test_handle_import_binary_rpm_with_create_draft(
self,
activate_session_mock,
stderr,
stdout):
"""Test handle_import binary RPM import with creating draft build case."""
arguments = ['/path/to/bash-4.4.12-5.fc26.rpm', '--create-draft']
options = mock.MagicMock()
session = mock.MagicMock()
nvr = '%(name)s-%(version)s-%(release)s' % self.rpm_header
# Case 1. import bin rpm with --create-draft
# result: Aborting import
expected = "Will create draft build instead if desired nvr doesn't exist\n"
expected += "Missing srpm for draft build creating with target nvr: %s\n" % nvr
expected += "Aborting import\n"
self.__skip_import_test(
options, session, arguments,
rpm_header=self.rpm_header,
expected=expected,
getrpm_called=False)
@mock.patch('sys.stdout', new_callable=six.StringIO)
@mock.patch('sys.stderr', new_callable=six.StringIO)
@mock.patch('koji_cli.commands.activate_session')
def test_handle_import_src_bin_rpm_with_create_draft(
self,
activate_session_mock,
stderr,
stdout):
"""Test handle_import SRPM & RPM import with creating draft build case."""
arguments = ['/path/to/bash-4.4.12-5.fc26.rpm', '/path/to/bash-4.4.12-5.fc26.src.rpm',
'--create-draft']
options = mock.MagicMock()
session = mock.MagicMock()
session.getRPM.return_value = None
session.importRPM.return_value = {'build': {'nvr': 'a-draft-build', 'draft': True}}
nvr = '%(name)s-%(version)s-%(release)s' % self.srpm_header
# Case 1. import src & bin rpm with --create-draft
# result: success
expected = "Will create draft build instead if desired nvr doesn't exist\n"
expected += "Will create draft build with target nvr: %s while importing\n" % nvr
expected += "uploading %s... done\n" % arguments[1]
expected += "importing %s... done\n" % arguments[1]
expected += "Draft build: a-draft-build created\n"
expected += "uploading %s... done\n" % arguments[0]
expected += "importing %s... done\n" % arguments[0]
self.__do_import_test(
options, session, arguments,
rpm_headers=[self.rpm_header, self.srpm_header],
expected=expected,
getrpm_calls=[
mock.call(
{'name': 'bash', 'version': '4.4.12', 'release': '5.fc26', 'arch': 'x86_64'},
build=session.importRPM.return_value['build']
)
],
import_rpm_calls=[
mock.call(
self.fake_srv_dir, os.path.basename(arguments[1]), draft=True
),
mock.call(
self.fake_srv_dir, os.path.basename(arguments[0]),
build=session.importRPM.return_value['build'], draft=True
)
])
@mock.patch('sys.stdout', new_callable=six.StringIO)
@mock.patch('sys.stderr', new_callable=six.StringIO)
@mock.patch('koji_cli.commands.activate_session')
def test_handle_import_src_rpm_with_specified_draft_build(
self,
activate_session_mock,
stderr,
stdout):
"""Test handle_import SRPM import with --draft-build case."""
arguments = ['/path/to/bash-4.4.12-5.fc26.rpm', '--draft-build', 'a-draft-build']
options = mock.MagicMock()
session = mock.MagicMock()
build = {
'name': 'bash',
'version': '4.4.12',
'release': '5.fc26#draft_123',
'nvr': 'bash-4.4.12-5.fc26',
'draft': True,
'extra': {
'draft': {
'target_release': '5.fc26'
}
},
'state': self.bstate['COMPLETE']
}
session.getBuild.return_value = build
# Case 1. import src rpm with --draft-build
# result: success
expected = "uploading %s... done\n" % arguments[0]
expected += "importing %s... done\n" % arguments[0]
self.__do_import_test(
options, session, arguments,
rpm_header=self.srpm_header,
expected=expected,
getrpm_calls=[mock.call(
{'name': 'bash', 'version': '4.4.12', 'release': '5.fc26', 'arch': 'src'},
build=build)],
import_opts={'build': build, 'draft': True})
@mock.patch('sys.stdout', new_callable=six.StringIO)
@mock.patch('sys.stderr', new_callable=six.StringIO)
@mock.patch('koji_cli.commands.activate_session')
def test_handle_import_binary_rpm_with_specified_draft_build(
self,
activate_session_mock,
stderr,
stdout):
"""Test handle_import RPM import with --draft-build case."""
arguments = ['/path/to/bash-4.4.12-5.fc26.rpm', '--draft-build', 'a-draft-build']
options = mock.MagicMock()
session = mock.MagicMock()
session.getRPM.return_value = None
build = {
'name': 'bash',
'version': '4.4.12',
'release': '5.fc26#draft_123',
'nvr': 'bash-4.4.12-5.fc26',
'draft': True,
'extra': {
'draft': {
'target_release': '5.fc26'
}
},
'state': self.bstate['COMPLETE']
}
session.getBuild.return_value = build
# Case 1. import bin rpm with --draft-build
# result: success
expected = "uploading %s... done\n" % arguments[0]
expected += "importing %s... done\n" % arguments[0]
self.__do_import_test(
options, session, arguments,
rpm_header=self.rpm_header,
expected=expected,
getrpm_calls=[mock.call(
{'name': 'bash', 'version': '4.4.12', 'release': '5.fc26', 'arch': 'x86_64'},
build=build)],
import_opts={'build': build, 'draft': True})
def test_handle_import_specified_draft_build_invalid(self):
"""Test handle_import RPM import with --draft-build case."""
arguments = ['/path/to/bash-4.4.12-5.fc26.rpm', '--draft-build', '286']
options = mock.MagicMock()
session = mock.MagicMock()
cases = [
# build, stderr
(None, "No such build: 286"),
({'draft': False, 'nvr': 'a-bad-draft'}, "a-bad-draft is not a draft build"),
({'draft': True, 'nvr': 'a-bad-draft', 'state': koji.BUILD_STATES['DELETED']},
"draft build a-bad-draft is expected as COMPLETE, got DELETED"),
({'draft': True, 'nvr': 'a-bad-draft', 'state': koji.BUILD_STATES['COMPLETE'],
'extra': {'draft': {'no_target_release': 'omg'}}},
"Invalid draft build: a-bad-draft, no draft.target_release found in extra")
]
for build, expected in cases:
session.getBuild.return_value = build
# result: error
expected += "\n"
self.assert_system_exit(
handle_import,
options,
session,
arguments,
stderr=expected,
activate_session=None,
exit_code=1)
options.reset_mock()
session.reset_mock()
def test_handle_import_help(self):
"""Test handle_import function help message"""
self.assert_help(
@ -691,6 +932,8 @@ Options:
--create-build Auto-create builds as needed
--src-epoch=SRC_EPOCH
When auto-creating builds, use this epoch
--create-draft Auto-create draft builds instead as needed
--draft-build=NVR|ID The target draft build to import to
""" % self.progname)

View file

@ -609,6 +609,8 @@ Options:
pattern)
--owner=OWNER List builds built by this owner
--volume=VOLUME List builds by volume ID
--draft-only Only list draft builds
--no-draft Only list regular builds
-k FIELD, --sort-key=FIELD
Sort the list by the named field. Allowed sort keys:
build_id, owner_name, state

View file

@ -42,6 +42,15 @@ class TestCliListTagged(utils.CliTestCase):
'release': '1.el6',
'arch': 'x86_64',
'sigkey': 'sigkey',
'extra': None},
{'id': 102,
'build_id': 2,
'name': 'rpmA',
'version': '0.0.1',
'release': '2.el6',
'arch': 'x86_64',
'sigkey': 'sigkey',
'draft': True,
'extra': None}
], [{'id': 1,
'name': 'packagename',
@ -50,6 +59,15 @@ class TestCliListTagged(utils.CliTestCase):
'nvr': 'n-v-r',
'tag_name': 'tag',
'owner_name': 'owner',
'extra': 'extra-value-2'},
{'id': 2,
'name': 'packagename',
'version': 'version',
'release': '2.el6#draft_2',
'nvr': 'n-v-r',
'draft': True,
'tag_name': 'tag',
'owner_name': 'owner',
'extra': 'extra-value-2'}]]
self.session.listTagged.return_value = [{'id': 1,
'name': 'packagename',
@ -77,13 +95,15 @@ Build Tag Built by
---------------------------------------- -------------------- ----------------
n-v-r tag owner
"""
args = [self.tag, self.pkg, '--latest', '--inherit', '--event', str(self.event_id)]
args = [self.tag, self.pkg, '--no-draft', '--latest', '--inherit',
'--event', str(self.event_id)]
anon_handle_list_tagged(self.options, self.session, args)
self.ensure_connection_mock.assert_called_once_with(self.session, self.options)
self.session.getTag.assert_called_once_with(self.tag, event=self.event_id)
self.session.listTagged.assert_called_once_with(
self.tag, event=self.event_id, inherit=True, latest=True, package=self.pkg)
self.tag, event=self.event_id, inherit=True, latest=True, package=self.pkg,
draft=2)
self.session.listTaggedRPMS.assert_not_called()
self.assert_console_message(stdout, expected)
@ -94,14 +114,14 @@ n-v-r tag owner
---------------------------------------- -------------------- ----------------
/mnt/koji/packages/packagename/version/1.el6 tag owner
"""
args = [self.tag, self.pkg, '--latest', '--inherit', '--paths']
args = [self.tag, self.pkg, '--latest', '--inherit', '--paths', '--draft-only']
anon_handle_list_tagged(self.options, self.session, args)
self.assert_console_message(stdout, expected)
self.ensure_connection_mock.assert_called_once_with(self.session, self.options)
self.session.getTag.assert_called_once_with(self.tag, event=None)
self.session.listTagged.assert_called_once_with(
self.tag, inherit=True, latest=True, package=self.pkg)
self.tag, inherit=True, latest=True, package=self.pkg, draft=1)
self.session.listTaggedRPMS.assert_not_called()
@mock.patch('sys.stdout', new_callable=six.StringIO)
@ -109,6 +129,7 @@ n-v-r tag owner
def test_list_tagged_rpms(self, event_from_opts_mock, stdout):
expected = """sigkey rpmA-0.0.1-1.el6.noarch
sigkey rpmA-0.0.1-1.el6.x86_64
sigkey rpmA-0.0.1-2.el6.x86_64 (#draft_2)
"""
args = [self.tag, self.pkg, '--latest-n=3', '--rpms', '--sigs',
'--arch=x86_64', '--arch=noarch']
@ -129,6 +150,7 @@ sigkey rpmA-0.0.1-1.el6.x86_64
def test_list_tagged_rpms_paths(self, event_from_opts_mock, stdout, os_path_exists, isdir):
expected = """/mnt/koji/packages/packagename/version/1.el6/noarch/rpmA-0.0.1-1.el6.noarch.rpm
/mnt/koji/packages/packagename/version/1.el6/x86_64/rpmA-0.0.1-1.el6.x86_64.rpm
/mnt/koji/packages/packagename/version/2.el6#draft_2/x86_64/rpmA-0.0.1-2.el6.x86_64.rpm
"""
args = [self.tag, self.pkg, '--latest-n=3', '--rpms', '--arch=x86_64', '--paths']
@ -233,6 +255,15 @@ n-v-r tag group
self.session.listTaggedRPMS.assert_not_called()
self.session.listTagged.assert_not_called()
def test_list_tagged_draft_opts_conflict(self):
self.assert_system_exit(
anon_handle_list_tagged,
self.options, self.session, ['--draft-only', '--no-draft', 'tag', 'pkg1'],
stderr=self.format_error_message("--draft-only conflicts with --no-draft"),
activate_session=None,
exit_code=2)
self.ensure_connection_mock.assert_not_called()
def test_list_tagged_tag_not_found(self):
self.session.getTag.return_value = None
self.assert_system_exit(
@ -267,4 +298,6 @@ Options:
--event=EVENT# query at event
--ts=TIMESTAMP query at last event before timestamp
--repo=REPO# query at event for a repo
--draft-only Only list draft builds/rpms
--no-draft Only list regular builds/rpms
""" % self.progname)

View file

@ -53,6 +53,17 @@ class TestRpminfo(utils.CliTestCase):
'version': '1.1',
'payloadhash': 'b2b95550390e5f213fc25f33822425f7',
'size': 7030}
self.listrpminfos = [{'arch': 'src',
'build_id': 1,
'buildroot_id': 3,
'buildtime': 1615877809,
'epoch': 7,
'id': 290,
'name': 'test-rpm',
'release': '11',
'version': '1.1',
'payloadhash': 'b2b95550390e5f213fc25f33822425f7',
'size': 7030}]
self.error_format = """Usage: %s rpminfo [options] <n-v-r.a> [<n-v-r.a> ...]
(Specify the --help global option for a list of other help options)
@ -74,9 +85,11 @@ class TestRpminfo(utils.CliTestCase):
self.session.listBuildroots.return_value = [self.buildroot_info]
self.session.getBuild.return_value = self.buildinfo
self.session.getRPM.return_value = self.getrpminfo
self.session.listRPMs.return_value = self.listrpminfos
expected_output = """RPM: 7:test-rpm-1.1-11.noarch [294]
Build: test-rpm-1.1-11 [1]
RPM Path: /mnt/koji/packages/test-rpm/1.1/11/noarch/test-rpm-1.1-11.noarch.rpm
SRPM: 7:test-rpm-1.1-11 [1]
SRPM: 7:test-rpm-1.1-11 [290]
SRPM Path: /mnt/koji/packages/test-rpm/1.1/11/src/test-rpm-1.1-11.src.rpm
Built: Tue, 16 Mar 2021 06:56:49 UTC
SIGMD5: b2b95550390e5f213fc25f33822425f7
@ -98,6 +111,8 @@ Used in 1 buildroots:
rpmID=self.getrpminfo['id'])
self.session.getBuild.assert_called_once_with(self.getrpminfo['build_id'])
self.session.getRPM.assert_called_once_with(rpm_nvra)
self.session.listRPMs.assert_called_once_with(buildID=self.getrpminfo['build_id'],
arches='src')
def test_handle_rpminfo_non_exist_nvra(self):
rpm_nvra = 'test-rpm-nvra.arch'
@ -119,9 +134,11 @@ Used in 1 buildroots:
self.session.listBuildroots.return_value = [self.buildroot_info]
self.session.getBuild.return_value = self.buildinfo
self.session.getRPM.side_effect = [None, self.getrpminfo]
self.session.listRPMs.return_value = self.listrpminfos
expected_output = """RPM: 7:test-rpm-1.1-11.noarch [294]
Build: test-rpm-1.1-11 [1]
RPM Path: /mnt/koji/packages/test-rpm/1.1/11/noarch/test-rpm-1.1-11.noarch.rpm
SRPM: 7:test-rpm-1.1-11 [1]
SRPM: 7:test-rpm-1.1-11 [290]
SRPM Path: /mnt/koji/packages/test-rpm/1.1/11/src/test-rpm-1.1-11.src.rpm
Built: Tue, 16 Mar 2021 06:56:49 UTC
SIGMD5: b2b95550390e5f213fc25f33822425f7
@ -150,6 +167,45 @@ Used in 1 buildroots:
rpmID=self.getrpminfo['id'])
self.session.getBuild.assert_called_once_with(self.getrpminfo['build_id'])
self.assertEqual(self.session.getRPM.call_count, 2)
self.session.listRPMs.assert_called_once_with(buildID=self.getrpminfo['build_id'],
arches='src')
@mock.patch('sys.stdout', new_callable=StringIO)
def test_handle_rpminfo_with_build(self, stdout):
rpm_nvra = 'test-rpm-1.1-11.noarch'
self.session.getBuildroot.return_value = self.buildroot_info
self.session.listBuildroots.return_value = [self.buildroot_info]
self.session.getBuild.return_value = self.buildinfo
self.session.getRPM.return_value = self.getrpminfo
self.session.listRPMs.return_value = self.listrpminfos
expected_output = """RPM: 7:test-rpm-1.1-11.noarch [294]
Build: test-rpm-1.1-11 [1]
RPM Path: /mnt/koji/packages/test-rpm/1.1/11/noarch/test-rpm-1.1-11.noarch.rpm
SRPM: 7:test-rpm-1.1-11 [290]
SRPM Path: /mnt/koji/packages/test-rpm/1.1/11/src/test-rpm-1.1-11.src.rpm
Built: Tue, 16 Mar 2021 06:56:49 UTC
SIGMD5: b2b95550390e5f213fc25f33822425f7
Size: 7030
Build ID: 1
Buildroot: 3 (tag test-tag, arch x86_64, repo 2)
Build Host: kojibuilder
Build Task: 10
Used in 1 buildroots:
id build tag arch build host
-------- ---------------------------- -------- -----------------------------
3 test-tag x86_64 kojibuilder
"""
anon_handle_rpminfo(self.options, self.session, ['--buildroot', '--build', 'any', rpm_nvra])
self.assert_console_message(stdout, expected_output)
self.session.getBuildroot.assert_called_once_with(self.getrpminfo['buildroot_id'])
self.session.listBuildroots.assert_called_once_with(queryOpts={'order': 'buildroot.id'},
rpmID=self.getrpminfo['id'])
self.session.getBuild.assert_called_once_with(self.getrpminfo['build_id'])
self.session.getRPM.assert_called_once_with(rpm_nvra, build='any')
self.session.listRPMs.assert_called_once_with(buildID=self.getrpminfo['build_id'],
arches='src')
def test_rpminfo_without_option(self):
arguments = []
@ -171,6 +227,7 @@ Used in 1 buildroots:
(Specify the --help global option for a list of other help options)
Options:
-h, --help show this help message and exit
--buildroots show buildroots the rpm was used in
-h, --help show this help message and exit
--buildroots show buildroots the rpm was used in
--build=NVR|ID show the rpm(s) in the build
""" % self.progname)

View file

@ -237,6 +237,7 @@ Options:
--wait Wait on build, even if running in the background
--nowait Don't wait on build
--background Run the build at a lower priority
--draft Build draft build instead
""" % self.progname)

View file

@ -16,6 +16,10 @@ class TestGetRPM(DBQueryTestCase):
self.exports = kojihub.RootExports()
self.context = mock.patch('kojihub.kojihub.context').start()
self.get_external_repo_id = mock.patch('kojihub.kojihub.get_external_repo_id').start()
self.find_build_id = mock.patch('kojihub.kojihub.find_build_id').start()
def tearDown(self):
mock.patch.stopall()
def test_wrong_type_rpminfo(self):
rpminfo = ['test-user']
@ -31,11 +35,10 @@ class TestGetRPM(DBQueryTestCase):
self.assertEqual(len(self.queries), 1)
query = self.queries[0]
str(query)
self.assertEqual(query.tables, ['rpminfo'])
columns = ['rpminfo.id', 'build_id', 'buildroot_id', 'rpminfo.name', 'version', 'release',
'epoch', 'arch', 'external_repo_id', 'external_repo.name', 'payloadhash',
'size', 'buildtime', 'metadata_only', 'extra']
'epoch', 'arch', 'draft', 'external_repo_id', 'external_repo.name',
'payloadhash', 'size', 'buildtime', 'metadata_only', 'extra']
self.assertEqual(set(query.columns), set(columns))
self.assertEqual(query.clauses, ['external_repo_id = 0', "rpminfo.id=%(id)s"])
self.assertEqual(query.joins,
@ -50,11 +53,10 @@ class TestGetRPM(DBQueryTestCase):
self.assertEqual(len(self.queries), 1)
query = self.queries[0]
str(query)
self.assertEqual(query.tables, ['rpminfo'])
columns = ['rpminfo.id', 'build_id', 'buildroot_id', 'rpminfo.name', 'version', 'release',
'epoch', 'arch', 'external_repo_id', 'external_repo.name', 'payloadhash',
'size', 'buildtime', 'metadata_only', 'extra']
'epoch', 'arch', 'draft', 'external_repo_id', 'external_repo.name',
'payloadhash', 'size', 'buildtime', 'metadata_only', 'extra']
self.assertEqual(set(query.columns), set(columns))
self.assertEqual(query.clauses, ["rpminfo.id=%(id)s"])
self.assertEqual(query.joins,
@ -70,11 +72,10 @@ class TestGetRPM(DBQueryTestCase):
self.assertEqual(len(self.queries), 1)
query = self.queries[0]
str(query)
self.assertEqual(query.tables, ['rpminfo'])
columns = ['rpminfo.id', 'build_id', 'buildroot_id', 'rpminfo.name', 'version', 'release',
'epoch', 'arch', 'external_repo_id', 'external_repo.name', 'payloadhash',
'size', 'buildtime', 'metadata_only', 'extra']
'epoch', 'arch', 'draft', 'external_repo_id', 'external_repo.name',
'payloadhash', 'size', 'buildtime', 'metadata_only', 'extra']
self.assertEqual(set(query.columns), set(columns))
self.assertEqual(query.clauses, ["rpminfo.id=%(id)s"])
self.assertEqual(query.joins,
@ -87,11 +88,10 @@ class TestGetRPM(DBQueryTestCase):
self.assertEqual(len(self.queries), 1)
query = self.queries[0]
str(query)
self.assertEqual(query.tables, ['rpminfo'])
columns = ['rpminfo.id', 'build_id', 'buildroot_id', 'rpminfo.name', 'version', 'release',
'epoch', 'arch', 'external_repo_id', 'external_repo.name', 'payloadhash',
'size', 'buildtime', 'metadata_only', 'extra']
'epoch', 'arch', 'draft', 'external_repo_id', 'external_repo.name',
'payloadhash', 'size', 'buildtime', 'metadata_only', 'extra']
self.assertEqual(set(query.columns), set(columns))
self.assertEqual(query.clauses, ["rpminfo.name=%(name)s AND version=%(version)s "
"AND release=%(release)s AND arch=%(arch)s"])
@ -110,17 +110,36 @@ class TestGetRPM(DBQueryTestCase):
self.assertEqual(len(self.queries), 1)
query = self.queries[0]
str(query)
self.assertEqual(query.tables, ['rpminfo'])
columns = ['rpminfo.id', 'build_id', 'buildroot_id', 'rpminfo.name', 'version', 'release',
'epoch', 'arch', 'external_repo_id', 'external_repo.name', 'payloadhash',
'size', 'buildtime', 'metadata_only', 'extra']
'epoch', 'arch', 'draft', 'external_repo_id', 'external_repo.name',
'payloadhash', 'size', 'buildtime', 'metadata_only', 'extra']
self.assertEqual(set(query.columns), set(columns))
self.assertEqual(query.clauses,
["external_repo_id = %(external_repo_id)i", "rpminfo.id=%(id)s"])
self.assertEqual(query.joins,
['external_repo ON rpminfo.external_repo_id = external_repo.id'])
self.assertEqual(query.values, rpminfo_data)
def test_rpm_info_with_build(self):
rpminfo = {'id': 123, 'name': 'testrpm-1.23-4.x86_64.rpm', 'build_id': 101}
self.find_build_id.return_value = 101
rpminfo_data = rpminfo.copy()
kojihub.get_rpm(rpminfo, multi=True, build='any')
self.assertEqual(len(self.queries), 1)
query = self.queries[0]
self.assertEqual(query.tables, ['rpminfo'])
columns = ['rpminfo.id', 'build_id', 'buildroot_id', 'rpminfo.name', 'version', 'release',
'epoch', 'arch', 'draft', 'external_repo_id', 'external_repo.name',
'payloadhash', 'size', 'buildtime', 'metadata_only', 'extra']
self.assertEqual(set(query.columns), set(columns))
self.assertEqual(query.clauses,
["rpminfo.build_id = %(build_id)s", "rpminfo.id=%(id)s"])
self.assertEqual(query.joins,
['external_repo ON rpminfo.external_repo_id = external_repo.id'])
self.assertEqual(query.values, rpminfo_data)
class TestGetRPMHeaders(unittest.TestCase):

View file

@ -39,8 +39,8 @@ class TestGetBuild(DBQueryTestCase):
self.assertEqual(query.columns,
['build.id', 'build.cg_id', 'build.completion_time',
"date_part('epoch', build.completion_time)", 'events.id', 'events.time',
"date_part('epoch', events.time)", 'build.epoch', 'build.extra',
'build.id', 'package.name',
"date_part('epoch', events.time)", 'build.draft', 'build.epoch',
'build.extra', 'build.id', 'package.name',
"package.name || '-' || build.version || '-' || build.release",
'users.id', 'users.name', 'package.id', 'package.name', 'build.release',
'build.source', 'build.start_time',
@ -64,8 +64,8 @@ class TestGetBuild(DBQueryTestCase):
self.assertEqual(query.columns,
['build.id', 'build.cg_id', 'build.completion_time',
"date_part('epoch', build.completion_time)", 'events.id', 'events.time',
"date_part('epoch', events.time)", 'build.epoch', 'build.extra',
'build.id', 'package.name',
"date_part('epoch', events.time)", 'build.draft', 'build.epoch',
'build.extra', 'build.id', 'package.name',
"package.name || '-' || build.version || '-' || build.release",
'users.id', 'users.name', 'package.id', 'package.name', 'build.release',
'build.source', 'build.start_time',
@ -111,8 +111,8 @@ class TestGetBuild(DBQueryTestCase):
self.assertEqual(query.columns,
['build.id', 'build.cg_id', 'build.completion_time',
"date_part('epoch', build.completion_time)", 'events.id', 'events.time',
"date_part('epoch', events.time)", 'build.epoch', 'build.extra',
'build.id', 'package.name',
"date_part('epoch', events.time)", 'build.draft', 'build.epoch',
'build.extra', 'build.id', 'package.name',
"package.name || '-' || build.version || '-' || build.release",
'users.id', 'users.name', 'package.id', 'package.name', 'build.release',
'build.source', 'build.start_time',
@ -138,8 +138,8 @@ class TestGetBuild(DBQueryTestCase):
self.assertEqual(query.columns,
['build.id', 'build.cg_id', 'build.completion_time',
"date_part('epoch', build.completion_time)", 'events.id', 'events.time',
"date_part('epoch', events.time)", 'build.epoch', 'build.extra',
'build.id', 'package.name',
"date_part('epoch', events.time)", 'build.draft', 'build.epoch',
'build.extra', 'build.id', 'package.name',
"package.name || '-' || build.version || '-' || build.release",
'users.id', 'users.name', 'package.id', 'package.name', 'build.release',
'build.source', 'build.start_time',

View file

@ -22,7 +22,8 @@ class TestGetNextRelease(DBQueryTestCase):
self.assertEqual(query.tables, ['build'])
self.assertEqual(query.joins, ['package ON build.pkg_id = package.id'])
self.assertEqual(query.clauses,
['name = %(name)s', 'state in %(states)s', 'version = %(version)s'])
['NOT draft', 'name = %(name)s', 'state in %(states)s',
'version = %(version)s'])
self.assertEqual(query.values, {'name': self.binfo['name'],
'version': self.binfo['version'],
'states': (1, 2, 0)

View file

@ -92,6 +92,7 @@ class TestImportBuild(unittest.TestCase):
fields = [
'completion_time',
'draft',
'epoch',
'extra',
'id',
@ -123,6 +124,7 @@ class TestImportBuild(unittest.TestCase):
'release': 'release',
'pkg_id': mock.ANY,
'id': mock.ANY,
'draft': False
}
self._dml.assert_called_once_with(statement, values)

View file

@ -42,11 +42,12 @@ class TestImportRPM(unittest.TestCase):
1003: 'epoch',
1006: 'buildtime',
1022: 'arch',
1044: 'name-version-release.arch',
1044: 'name-version-release.src.rpm',
1106: 'sourcepackage',
261: 'payload hash',
}
self.get_build = mock.patch('kojihub.kojihub.get_build').start()
self.new_build = mock.patch('kojihub.kojihub.new_build').start()
self.get_rpm_header = mock.patch('koji.get_rpm_header').start()
self.new_typed_build = mock.patch('kojihub.kojihub.new_typed_build').start()
self.nextval = mock.patch('kojihub.kojihub.nextval').start()
@ -65,6 +66,7 @@ class TestImportRPM(unittest.TestCase):
kojihub.import_rpm("this does not exist")
def test_import_rpm_failed_build(self):
self.os_path_basename.return_value = 'name-version-release.arch.rpm'
self.get_rpm_header.return_value = self.rpm_header_retval
self.get_build.return_value = {
'state': koji.BUILD_STATES['FAILED'],
@ -72,9 +74,11 @@ class TestImportRPM(unittest.TestCase):
'version': 'version',
'release': 'release',
}
with self.assertRaises(koji.GenericError):
with self.assertRaises(koji.GenericError) as cm:
kojihub.import_rpm(self.filename)
self.assertEqual("Build is FAILED: name-version-release", str(cm.exception))
self.assertEqual(len(self.inserts), 0)
def test_import_rpm_completed_build(self):
self.os_path_basename.return_value = 'name-version-release.arch.rpm'
@ -94,6 +98,7 @@ class TestImportRPM(unittest.TestCase):
'name': 'name',
'arch': 'arch',
'buildtime': 'buildtime',
'draft': False,
'payloadhash': '7061796c6f61642068617368',
'epoch': 'epoch',
'version': 'version',
@ -114,7 +119,7 @@ class TestImportRPM(unittest.TestCase):
retval = copy.copy(self.rpm_header_retval)
retval.update({
'filename': 'name-version-release.arch.rpm',
1044: 'name-version-release.src',
1044: 'name-version-release.src.rpm.bad',
1022: 'src',
1106: 1,
})
@ -133,6 +138,7 @@ class TestImportRPM(unittest.TestCase):
'name': 'name',
'arch': 'src',
'buildtime': 'buildtime',
'draft': False,
'payloadhash': '7061796c6f61642068617368',
'epoch': 'epoch',
'version': 'version',
@ -149,10 +155,9 @@ class TestImportRPM(unittest.TestCase):
self.assertEqual(insert.rawdata, {})
def test_non_exist_file(self):
basename = 'rpm-1-34'
self.os_path_exists.return_value = False
with self.assertRaises(koji.GenericError) as cm:
kojihub.import_rpm(self.filename, basename)
kojihub.import_rpm(self.filename)
self.assertEqual(f"No such file: {self.filename}", str(cm.exception))
self.assertEqual(len(self.inserts), 0)
@ -172,3 +177,211 @@ class TestImportRPM(unittest.TestCase):
kojihub.import_rpm(self.src_filename)
self.assertEqual("No such build", str(cm.exception))
self.assertEqual(len(self.inserts), 0)
def test_import_draft_rpm_completed_build(self):
self.os_path_basename.return_value = 'name-version-release.arch.rpm'
self.get_rpm_header.return_value = self.rpm_header_retval
self.get_build.return_value = {
'state': koji.BUILD_STATES['COMPLETE'],
'name': 'name',
'version': 'version',
'release': 'release',
'id': 12345,
}
self.nextval.return_value = 9876
kojihub.import_rpm(self.filename)
data = {
'build_id': 12345,
'name': 'name',
'arch': 'arch',
'buildtime': 'buildtime',
'draft': False,
'payloadhash': '7061796c6f61642068617368',
'epoch': 'epoch',
'version': 'version',
'buildroot_id': None,
'release': 'release',
'external_repo_id': 0,
'id': 9876,
'size': 0,
}
self.assertEqual(len(self.inserts), 1)
insert = self.inserts[0]
self.assertEqual(insert.table, 'rpminfo')
self.assertEqual(insert.data, data)
self.assertEqual(insert.rawdata, {})
def test_import_draft_conflict(self):
with self.assertRaises(koji.GenericError) as cm:
kojihub.import_rpm(self.filename, buildinfo={'id': 1024, 'draft': False}, draft=True)
self.assertEqual("draft property: False of build: 1024 mismatch, True is expected",
str(cm.exception))
self.assertEqual(len(self.inserts), 0)
def test_import_draft_rpm_without_buildinfo(self):
self.os_path_basename.return_value = 'name-version-release.arch.rpm'
self.get_rpm_header.return_value = self.rpm_header_retval
with self.assertRaises(koji.GenericError) as cm:
kojihub.import_rpm(self.filename, draft=True)
self.assertEqual(f"Cannot import draft rpm: {self.os_path_basename.return_value}"
" without specifying a build", str(cm.exception))
self.assertEqual(len(self.inserts), 0)
def test_import_draft_rpm_non_extra_target_release(self):
self.os_path_basename.return_value = 'name-version-release.arch.rpm'
self.get_rpm_header.return_value = self.rpm_header_retval
buildinfo = {
'state': koji.BUILD_STATES['DELETED'],
'name': 'name',
'version': 'version',
'release': 'release',
'id': 12345,
'draft': True
}
with self.assertRaises(koji.GenericError) as cm:
kojihub.import_rpm(self.filename, buildinfo=buildinfo, draft=True)
self.assertEqual(
f'target release of draft build not found in extra of build: {buildinfo}',
str(cm.exception)
)
self.assertEqual(len(self.inserts), 0)
def test_import_draft_rpm_valid(self):
self.os_path_basename.return_value = 'name-version-release.arch.rpm'
self.get_rpm_header.return_value = self.rpm_header_retval
buildinfo = {
'state': koji.BUILD_STATES['COMPLETE'],
'name': 'name',
'version': 'version',
'release': 'release',
'id': 12345,
'draft': True,
'extra': {
'draft': {
'target_release': 'release'
}
}
}
self.nextval.return_value = 9876
kojihub.import_rpm(self.filename, buildinfo=buildinfo, draft=True)
data = {
'build_id': 12345,
'name': 'name',
'arch': 'arch',
'buildtime': 'buildtime',
'draft': True,
'payloadhash': '7061796c6f61642068617368',
'epoch': 'epoch',
'version': 'version',
'buildroot_id': None,
'release': 'release',
'external_repo_id': 0,
'id': 9876,
'size': 0,
}
self.assertEqual(len(self.inserts), 1)
insert = self.inserts[0]
self.assertEqual(insert.table, 'rpminfo')
self.assertEqual(insert.data, data)
self.assertEqual(insert.rawdata, {})
def test_import_draft_srpm_with_buildinfo(self):
self.os_path_basename.return_value = 'name-version-release.src.rpm'
retval = copy.copy(self.rpm_header_retval)
retval.update({
'filename': 'name-version-release.src.rpm',
1044: 'name-version-release.src.rpm.bad',
1022: 'src',
1106: 1,
})
self.get_rpm_header.return_value = retval
buildinfo = {
'state': koji.BUILD_STATES['COMPLETE'],
'name': 'name',
'version': 'version',
'release': 'release',
'id': 12345,
'draft': True,
'extra': {
'draft': {
'target_release': 'release'
}
}
}
self.nextval.return_value = 9876
kojihub.import_rpm(self.src_filename, buildinfo=buildinfo, draft=True)
data = {
'build_id': 12345,
'name': 'name',
'arch': 'src',
'buildtime': 'buildtime',
'draft': True,
'payloadhash': '7061796c6f61642068617368',
'epoch': 'epoch',
'version': 'version',
'buildroot_id': None,
'release': 'release',
'external_repo_id': 0,
'id': 9876,
'size': 0,
}
self.assertEqual(len(self.inserts), 1)
insert = self.inserts[0]
self.assertEqual(insert.table, 'rpminfo')
self.assertEqual(insert.data, data)
self.assertEqual(insert.rawdata, {})
def test_import_draft_srpm_without_buildinfo(self):
self.os_path_basename.return_value = 'name-version-release.src.rpm'
retval = copy.copy(self.rpm_header_retval)
retval.update({
'filename': 'name-version-release.src.rpm',
1044: 'name-version-release.src.rpm.bad',
1022: 'src',
1106: 1,
})
self.get_rpm_header.return_value = retval
self.get_build.return_value = {
'state': koji.BUILD_STATES['COMPLETE'],
'name': 'name',
'version': 'version',
'release': 'release',
'id': 5566,
'draft': True,
'extra': {
'draft': {
'target_release': 'release'
}
}
}
self.new_build.return_value = 5566
self.nextval.return_value = 9876
kojihub.import_rpm(self.src_filename, draft=True)
data = {
'build_id': 5566,
'name': 'name',
'arch': 'src',
'buildtime': 'buildtime',
'draft': True,
'payloadhash': '7061796c6f61642068617368',
'epoch': 'epoch',
'version': 'version',
'buildroot_id': None,
'release': 'release',
'external_repo_id': 0,
'id': 9876,
'size': 0,
}
self.assertEqual(len(self.inserts), 1)
insert = self.inserts[0]
self.assertEqual(insert.table, 'rpminfo')
self.assertEqual(insert.data, data)
self.assertEqual(insert.rawdata, {})
self.get_build.assert_called_once_with(5566, strict=True)
self.assertEqual(self.get_build.call_count, 1)

View file

@ -17,6 +17,32 @@ class TestListBuilds(unittest.TestCase):
return query
def setUp(self):
# defaults
self.tables= ['build']
self.columns = [
'build.id', 'build.completion_time',
"date_part('epoch', build.completion_time)",
'events.id', 'events.time',
"date_part('epoch', events.time)",
'build.draft',
'build.epoch',
'build.extra', 'package.name',
"package.name || '-' || build.version || '-' || "
"build.release", 'users.id', 'users.name', 'package.id',
'package.name', 'build.release', 'build.source',
'build.start_time', "date_part('epoch', build.start_time)",
'build.state', 'build.task_id', 'build.version',
'volume.id', 'volume.name'
]
self.clauses = ['package.id = %(packageID)i']
self.joins = [
'LEFT JOIN events ON build.create_event = events.id',
'LEFT JOIN package ON build.pkg_id = package.id',
'LEFT JOIN volume ON build.volume_id = volume.id',
'LEFT JOIN users ON build.owner = users.id'
]
self.maxDiff = None
self.exports = kojihub.RootExports()
self.query_executeOne = mock.MagicMock()
@ -41,7 +67,8 @@ class TestListBuilds(unittest.TestCase):
'task_id': 879,
'version': '11',
'volume_id': 0,
'volume_name': 'DEFAULT'}]
'volume_name': 'DEFAULT',
'draft': False},]
def test_wrong_package(self):
package = 'test-package'
@ -58,26 +85,27 @@ class TestListBuilds(unittest.TestCase):
self.assertEqual(len(self.queries), 1)
args, kwargs = self.QueryProcessor.call_args
qp = QP(**kwargs)
self.assertEqual(qp.tables, ['build'])
self.assertEqual(qp.columns, ['build.id', 'build.completion_time',
"date_part('epoch', build.completion_time)",
'events.id', 'events.time',
"date_part('epoch', events.time)", 'build.epoch',
'build.extra', 'package.name',
"package.name || '-' || build.version || '-' || "
"build.release", 'users.id', 'users.name', 'package.id',
'package.name', 'build.release', 'build.source',
'build.start_time', "date_part('epoch', build.start_time)",
'build.state', 'build.task_id', 'build.version',
'volume.id', 'volume.name'])
self.assertEqual(qp.clauses, ['package.id = %(packageID)i'])
self.assertEqual(qp.joins, ['LEFT JOIN events ON build.create_event = events.id',
'LEFT JOIN package ON build.pkg_id = package.id',
'LEFT JOIN volume ON build.volume_id = volume.id',
'LEFT JOIN users ON build.owner = users.id'])
self.assertEqual(qp.tables, self.tables)
self.assertEqual(qp.columns, self.columns)
self.assertEqual(qp.clauses, self.clauses)
self.assertEqual(qp.joins, self.joins)
def test_wrong_user(self):
user = 'test-user'
self.get_user.return_value = None
rv = self.exports.listBuilds(userID=user)
self.assertEqual(rv, [])
def test_draft(self):
package = 'test-package'
package_id = 1
self.get_package_id.return_value = package_id
self.query_executeOne.return_value = None
self.exports.listBuilds(packageID=package, draft=1)
self.assertEqual(len(self.queries), 1)
args, kwargs = self.QueryProcessor.call_args
qp = QP(**kwargs)
self.assertEqual(qp.tables, self.tables)
self.assertEqual(qp.columns, self.columns)
self.assertEqual(qp.clauses, ['draft IS TRUE'] + self.clauses)
self.assertEqual(qp.joins, self.joins)

View file

@ -2,6 +2,7 @@ import mock
import unittest
import koji
from koji.util import dslice
import kojihub
IP = kojihub.InsertProcessor
@ -23,6 +24,7 @@ class TestNewBuild(unittest.TestCase):
self.get_build = mock.patch('kojihub.kojihub.get_build').start()
self.recycle_build = mock.patch('kojihub.kojihub.recycle_build').start()
self.context = mock.patch('kojihub.kojihub.context').start()
self.find_build_id = mock.patch('kojihub.kojihub.find_build_id').start()
def tearDown(self):
mock.patch.stopall()
@ -64,6 +66,7 @@ class TestNewBuild(unittest.TestCase):
'start_time': 'NOW',
'state': 1,
'task_id': None,
'draft': False,
'version': 'test_version',
'volume_id': 0
})
@ -156,3 +159,48 @@ class TestNewBuild(unittest.TestCase):
self.assertEqual(len(self.inserts), 0)
self.assertEqual("No such build extra data: %(extra)r" % data, str(cm.exception))
def test_draft(self):
data = {
'owner': 123456,
'name': 'test_name',
'version': 'test_version',
'release': 'test_release',
'epoch': 'test_epoch',
'draft': True
}
insert_data = {
'completion_time': 'NOW',
'epoch': 'test_epoch',
'extra': '{"draft": {"target_release": "test_release", "promoted": false}}',
'id': 108,
'owner': 123,
'pkg_id': 54,
'release': 'test_release#draft_108',
'source': None,
'start_time': 'NOW',
'state': 1,
'task_id': None,
'draft': True,
'version': 'test_version',
'volume_id': 0
}
self.nextval.return_value = 108
self.new_package.return_value = 54
self.get_user.return_value = {'id': 123}
self.find_build_id.return_value = None
kojihub.new_build(data)
self.assertEqual(len(self.inserts), 1)
insert = self.inserts[0]
self.assertEqual(insert.table, 'build')
self.assertEqual(insert.data, insert_data)
self.get_build.assert_called_once_with(108, strict=True)
self.assertEqual(self.get_build.call_count, 1)
self.find_build_id.assert_called_once_with(
{
'name': 'test_name',
'version': 'test_version',
'release': 'test_release#draft_108'
})

View file

@ -0,0 +1,179 @@
import datetime
import json
import mock
import unittest
import koji
import kojihub
UP = kojihub.UpdateProcessor
class TestPromoteBuild(unittest.TestCase):
def getUpdate(self, *args, **kwargs):
update = UP(*args, **kwargs)
update.execute = mock.MagicMock()
self.updates.append(update)
return update
def setUp(self):
self.exports = kojihub.RootExports()
self.UpdateProcessor = mock.patch('kojihub.kojihub.UpdateProcessor',
side_effect=self.getUpdate).start()
self.updates = []
self.context = mock.patch('kojihub.kojihub.context').start()
self.context.session.assertLogin = mock.MagicMock()
self.user = {'id': 1, 'name': 'jdoe'}
self.getLoggedInUser = mock.patch.object(self.exports, 'getLoggedInUser',
return_value=self.user).start()
self.get_build = mock.patch('kojihub.kojihub.get_build').start()
self.assert_policy = mock.patch('kojihub.kojihub.assert_policy').start()
self.apply_volume_policy = mock.patch('kojihub.kojihub.apply_volume_policy',
return_value=None).start()
self.move_and_symlink = mock.patch('kojihub.kojihub.move_and_symlink').start()
self.ensure_volume_symlink = mock.patch('kojihub.kojihub.ensure_volume_symlink').start()
self.list_tags = mock.patch('kojihub.kojihub.list_tags',
return_value=[{'id': 101}]).start()
self.set_tag_update = mock.patch('kojihub.kojihub.set_tag_update').start()
self.encode_datetime = mock.patch('kojihub.kojihub.encode_datetime', return_value='NOW').start()
self._now = datetime.datetime.now()
self._datetime = mock.patch('kojihub.kojihub.datetime.datetime').start()
self.now = self._datetime.now = mock.MagicMock(return_value=self._now)
self.draft_build = {
'id': 1,
'name': 'foo',
'version': 'bar',
'release': 'dftrel_1',
'extra': {
'draft': {
'promoted': False,
'target_release': 'tgtrel_1'
}},
'draft': True,
'volume_id': 99,
'volume_name': 'X'
}
self.new_build = {
# no check on the info
'id': 1,
'name': 'foo',
'version': 'bar',
'release': 'tgtrel_1',
'volume_name': 'X'
}
def tearDown(self):
mock.patch.stopall()
def test_promote_build_valid(self):
self.get_build.side_effect = [
self.draft_build,
None,
self.new_build
]
extra = json.dumps(
{
'draft': {
'promoted': True,
'target_release': 'tgtrel_1',
'old_release': 'dftrel_1',
'promotion_time': 'NOW',
'promotion_ts': self._now.timestamp(),
'promoter': self.user['name']
}
}
)
ret = self.exports.promoteBuild('a-draft-build', strict=True)
self.assertEqual(ret, self.new_build)
self.assertEqual(len(self.updates), 1)
update = self.updates[0]
self.assertEqual(update.table, 'build')
self.assertEqual(update.values, self.draft_build)
self.assertEqual(update.data, {'draft': False,
'release': 'tgtrel_1',
'extra': extra})
self.assertEqual(update.rawdata, {})
self.assertEqual(update.clauses, ['id=%(id)i'])
def test_promote_build_not_draft(self):
self.get_build.return_value = {'draft': False}
with self.assertRaises(koji.GenericError) as cm:
self.exports.promoteBuild('a-regular-build', strict=True)
self.assertEqual(str(cm.exception), "Not a draft build: {'draft': False}")
self.assertEqual(len(self.updates), 0)
ret = self.exports.promoteBuild('a-regular-build', strict=False)
self.assertIsNone(ret)
self.assertEqual(len(self.updates), 0)
def test_promote_build_no_target_release(self):
draft = {
'id': 1,
'name': 'foo',
'version': 'bar',
'release': 'dftrel_1',
'extra': {
'draft': {
'promoted': False
# no target_release
}},
'draft': True,
'volume_id': 99,
'volume_name': 'X'
}
self.get_build.return_value = draft
with self.assertRaises(koji.GenericError) as cm:
self.exports.promoteBuild('a-regular-build', strict=True)
self.assertEqual(str(cm.exception), f"draft.target_release not found in extra of build: {draft}")
self.assertEqual(len(self.updates), 0)
ret = self.exports.promoteBuild('a-regular-build', strict=False)
self.assertIsNone(ret)
self.assertEqual(len(self.updates), 0)
def test_promote_build_target_build_exists(self):
old = {
'id': 'any'
}
self.get_build.side_effect = [self.draft_build, old]
with self.assertRaises(koji.GenericError) as cm:
self.exports.promoteBuild('a-regular-build', strict=True)
self.assertEqual(str(cm.exception), f"Target build already exists: {old}")
self.assertEqual(len(self.updates), 0)
self.get_build.assert_called_with({
'name': 'foo',
'version': 'bar',
'release': 'tgtrel_1'
})
self.get_build.reset_mock()
self.get_build.side_effect = [self.draft_build, old]
ret = self.exports.promoteBuild('a-regular-build', strict=False)
self.assertIsNone(ret)
self.assertEqual(len(self.updates), 0)
def test_promote_build_volume_changed(self):
self.get_build.side_effect = [self.draft_build, None]
self.apply_volume_policy.return_value = {
'id': 100,
'name': 'Y'
}
with self.assertRaises(koji.GenericError) as cm:
self.exports.promoteBuild('a-regular-build', strict=True)
self.assertEqual(str(cm.exception), f"Denial as volume will be changed to Y")
self.assertEqual(len(self.updates), 0)
self.get_build.reset_mock()
self.get_build.side_effect = [self.draft_build, None]
ret = self.exports.promoteBuild('a-regular-build', strict=False)
self.assertIsNone(ret)
self.assertEqual(len(self.updates), 0)

View file

@ -31,7 +31,7 @@ class TestReadTaggedBuilds(unittest.TestCase):
self.tag_name = 'test-tag'
self.columns = ['tag.id', 'tag.name', 'build.id', 'build.version', 'build.release',
'build.epoch', 'build.state', 'build.completion_time', 'build.start_time',
'build.task_id', 'users.id', 'users.name', 'events.id', 'events.time',
'build.task_id', 'build.draft', 'users.id', 'users.name', 'events.id', 'events.time',
'volume.id', 'volume.name', 'package.id', 'package.name',
'package.name || \'-\' || build.version || \'-\' || build.release',
'tag_listing.create_event']
@ -40,6 +40,7 @@ class TestReadTaggedBuilds(unittest.TestCase):
('build.release', 'release'), ('build.epoch', 'epoch'),
('build.state', 'state'), ('build.completion_time', 'completion_time'),
('build.start_time', 'start_time'), ('build.task_id', 'task_id'),
('build.draft', 'draft'),
('users.id', 'owner_id'), ('users.name', 'owner_name'),
('events.id', 'creation_event_id'), ('events.time', 'creation_time'),
('volume.id', 'volume_id'), ('volume.name', 'volume_name'),
@ -54,7 +55,7 @@ class TestReadTaggedBuilds(unittest.TestCase):
'volume ON volume.id = build.volume_id',
'users ON users.id = build.owner', ]
self.aliases = ['tag_id', 'tag_name', 'id', 'build_id', 'version', 'release', 'epoch',
'state', 'completion_time', 'start_time', 'task_id', 'owner_id',
'state', 'completion_time', 'start_time', 'task_id', 'draft', 'owner_id',
'owner_name', 'creation_event_id', 'creation_time', 'volume_id',
'volume_name', 'package_id', 'package_name', 'name', 'nvr', 'create_event']
self.clauses = ['(tag_listing.active = TRUE)',
@ -83,7 +84,7 @@ class TestReadTaggedBuilds(unittest.TestCase):
'package': None, 'packages': self.package_list,
'queryOpts': {'order': '-create_event'}, 'st_complete': 1, 'tables': self.tables,
'tag': self.tag_name, 'tagid': self.tag_name, 'taglist': [self.tag_name],
'type': None
'type': None, 'draft': 3
}
self.assertEqual(query.tables, self.tables)
self.assertEqual(query.joins, self.joins)
@ -119,7 +120,7 @@ class TestReadTaggedBuilds(unittest.TestCase):
'package': self.pkg_name, 'packages': self.package_list,
'queryOpts': {'order': '-create_event'}, 'st_complete': 1, 'tables': self.tables,
'tag': self.tag_name, 'tagid': self.tag_name, 'taglist': [self.tag_name],
'type': 'maven'}
'type': 'maven', 'draft': 3}
self.assertEqual(query.tables, self.tables)
self.assertEqual(query.joins, joins)
self.assertEqual(set(query.columns), set(columns))
@ -148,7 +149,7 @@ class TestReadTaggedBuilds(unittest.TestCase):
'package': None, 'packages': self.package_list,
'queryOpts': {'order': '-create_event'}, 'st_complete': 1, 'tables': self.tables,
'tag': self.tag_name, 'tagid': self.tag_name, 'taglist': [self.tag_name],
'type': 'win'}
'type': 'win', 'draft': 3}
self.assertEqual(query.tables, self.tables)
self.assertEqual(query.joins, joins)
self.assertEqual(set(query.columns), set(columns))
@ -177,7 +178,7 @@ class TestReadTaggedBuilds(unittest.TestCase):
'package': None, 'packages': self.package_list,
'queryOpts': {'order': '-create_event'}, 'st_complete': 1, 'tables': self.tables,
'tag': self.tag_name, 'tagid': self.tag_name, 'taglist': [self.tag_name],
'type': 'image'}
'type': 'image', 'draft': 3}
self.assertEqual(query.tables, self.tables)
self.assertEqual(query.joins, joins)
self.assertEqual(set(query.columns), set(columns))
@ -212,10 +213,35 @@ class TestReadTaggedBuilds(unittest.TestCase):
'package': None, 'packages': self.package_list,
'queryOpts': {'order': '-create_event'}, 'st_complete': 1, 'tables': self.tables,
'tag': self.tag_name, 'tagid': self.tag_name, 'taglist': [self.tag_name],
'type': type}
'type': type, 'draft': 3}
self.assertEqual(query.tables, self.tables)
self.assertEqual(query.joins, joins)
self.assertEqual(set(query.columns), set(self.columns))
self.assertEqual(set(query.aliases), set(self.aliases))
self.assertEqual(set(query.clauses), set(self.clauses))
self.assertEqual(query.values, values)
def test_get_tagged_builds_draft(self):
self.readPackageList.return_value = self.package_list
kojihub.readTaggedBuilds(self.tag_name, draft=koji.DRAFT_FLAG.DRAFT)
self.assertEqual(len(self.queries), 1)
query = self.queries[0]
clauses = copy.deepcopy(self.clauses)
clauses.extend(['draft IS TRUE'])
values = {'clauses': clauses, 'event': None, 'extra': False, 'fields': self.fields,
'inherit': False, 'joins': self.joins, 'latest': False, 'owner': None,
'package': None, 'packages': self.package_list,
'queryOpts': {'order': '-create_event'}, 'st_complete': 1, 'tables': self.tables,
'tag': self.tag_name, 'tagid': self.tag_name, 'taglist': [self.tag_name],
'type': None, 'draft': koji.DRAFT_FLAG.DRAFT
}
self.assertEqual(query.tables, self.tables)
self.assertEqual(query.joins, self.joins)
self.assertEqual(set(query.columns), set(self.columns))
self.assertEqual(set(query.aliases), set(self.aliases))
self.assertEqual(set(query.clauses), set(clauses))
self.assertEqual(query.values, values)

View file

@ -29,12 +29,13 @@ class TestReadTaggedRPMS(unittest.TestCase):
self.readTaggedBuilds = mock.patch('kojihub.kojihub.readTaggedBuilds').start()
self.tag_name = 'test-tag'
self.columns = ['rpminfo.name', 'rpminfo.version', 'rpminfo.release', 'rpminfo.arch',
'rpminfo.id', 'rpminfo.epoch', 'rpminfo.payloadhash', 'rpminfo.size',
'rpminfo.buildtime', 'rpminfo.buildroot_id', 'rpminfo.build_id',
'rpminfo.metadata_only']
'rpminfo.id', 'rpminfo.epoch', 'rpminfo.draft', 'rpminfo.payloadhash',
'rpminfo.size', 'rpminfo.buildtime', 'rpminfo.buildroot_id',
'rpminfo.build_id', 'rpminfo.metadata_only']
self.joins = ['tag_listing ON rpminfo.build_id = tag_listing.build_id']
self.aliases = ['name', 'version', 'release', 'arch', 'id', 'epoch', 'payloadhash',
'size', 'buildtime', 'buildroot_id', 'build_id', 'metadata_only']
self.aliases = ['name', 'version', 'release', 'arch', 'id', 'epoch', 'draft',
'payloadhash', 'size', 'buildtime', 'buildroot_id', 'build_id',
'metadata_only']
self.clauses = ['(tag_listing.active = TRUE)',
'tag_id=%(tagid)s']
self.tables = ['rpminfo']
@ -101,3 +102,20 @@ class TestReadTaggedRPMS(unittest.TestCase):
self.assertEqual(set(query.aliases), set(aliases))
self.assertEqual(set(query.clauses), set(clauses))
self.assertEqual(query.values, values)
def test_get_tagged_rpms_draft(self):
self.readTaggedBuilds.return_value = self.build_list
kojihub.readTaggedRPMS(self.tag_name, draft=2, extra=False)
self.assertEqual(len(self.queries), 1)
query = self.queries[0]
clauses = copy.deepcopy(self.clauses)
clauses.extend(['rpminfo.draft IS NOT TRUE'])
self.assertEqual(query.tables, self.tables)
self.assertEqual(set(query.columns), set(self.columns))
self.assertEqual(set(query.joins), set(self.joins))
self.assertEqual(set(query.aliases), set(self.aliases))
self.assertEqual(set(query.clauses), set(clauses))
self.assertEqual(query.values, {})

View file

@ -44,6 +44,9 @@ class TestResetBuild(unittest.TestCase):
self.get_build = mock.patch('kojihub.kojihub.get_build').start()
self.context = mock.patch('kojihub.kojihub.context').start()
self.context.session.assertPerm = mock.MagicMock()
# don't remove anything unexpected
self.rmtree = mock.patch('koji.util.rmtree').start()
self.unlink = mock.patch('os.unlink').start()
self.build_id = 3
self.binfo = {'id': 3, 'state': koji.BUILD_STATES['COMPLETE'], 'name': 'test_nvr',
'nvr': 'test_nvr-3.3-20.el8', 'version': '3.3', 'release': '20',