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'])) % (data['name'], target_info['dest_tag_name']))
# TODO - more pre tests # TODO - more pre tests
archlist = self.getArchList(build_tag, h, extra=extra_arches) 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 # let the system know about the build we're attempting
if not self.opts.get('scratch'): if not self.opts.get('scratch'):
# scratch builds do not get imported # scratch builds do not get imported
@ -2176,6 +2178,8 @@ class WrapperRPMTask(BaseBuildTask):
data['extra'] = {'source': {'original_url': source['url']}} data['extra'] = {'source': {'original_url': source['url']}}
if opts.get('custom_user_metadata'): if opts.get('custom_user_metadata'):
data['extra']['custom_user_metadata'] = opts['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) self.logger.info("Reading package config for %(name)s" % data)
pkg_cfg = self.session.getPackageConfig(build_target['dest_tag'], data['name']) pkg_cfg = self.session.getPackageConfig(build_target['dest_tag'], data['name'])
if not opts.get('skip_tag'): 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 To: %(to_addrs)s\r
X-Koji-Package: %(pkg_name)s\r X-Koji-Package: %(pkg_name)s\r
X-Koji-NVR: %(nvr)s\r X-Koji-NVR: %(nvr)s\r
X-Koji-Draft: %(draft)s\r
X-Koji-User: %(user_name)s\r X-Koji-User: %(user_name)s\r
X-Koji-Status: %(status)s\r X-Koji-Status: %(status)s\r
%(tag_headers)s\r %(tag_headers)s\r
@ -5278,6 +5283,7 @@ Status: %(status)s\r
user = self.session.getUser(user_info) user = self.session.getUser(user_info)
pkg_name = build['package_name'] pkg_name = build['package_name']
nvr = koji.buildLabel(build) nvr = koji.buildLabel(build)
draft = build.get('draft', False)
user_name = user['name'] user_name = user['name']
from_addr = self.options.from_addr 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-Package: %(build_pkg_name)s\r
X-Koji-Builder: %(build_owner)s\r X-Koji-Builder: %(build_owner)s\r
X-Koji-Status: %(status)s\r X-Koji-Status: %(status)s\r
X-Koji-Draft: %(draft)s\r
\r \r
Package: %(build_nvr)s\r Package: %(build_nvr)s\r
Tag: %(dest_tag)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_nvr = koji.buildLabel(build)
build_id = build['id'] build_id = build['id']
build_owner = build['owner_name'] build_owner = build['owner_name']
draft = build.get('draft', False)
# target comes from session.py:_get_build_target() # target comes from session.py:_get_build_target()
dest_tag = None dest_tag = None
if target is not 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", parser.add_option("--custom-user-metadata", type="str",
help="Provide a JSON string of custom metadata to be deserialized and " help="Provide a JSON string of custom metadata to be deserialized and "
"stored under the build's extra.custom_user_metadata field") "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) (build_opts, args) = parser.parse_args(args)
if len(args) != 2: if len(args) != 2:
parser.error("Exactly two arguments (a build target and a SCM URL or srpm file) are " 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") parser.error("--no-/rebuild-srpm is only allowed for --scratch builds")
if build_opts.arch_override and not build_opts.scratch: if build_opts.arch_override and not build_opts.scratch:
parser.error("--arch_override is only allowed for --scratch builds") 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 = {} custom_user_metadata = {}
if build_opts.custom_user_metadata: if build_opts.custom_user_metadata:
try: try:
@ -618,7 +622,7 @@ def handle_build(options, session, args):
if build_opts.arch_override: if build_opts.arch_override:
opts['arch_override'] = koji.parse_arches(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', for key in ('skip_tag', 'scratch', 'repo_id', 'fail_fast', 'wait_repo', 'wait_builds',
'rebuild_srpm'): 'rebuild_srpm', 'draft'):
val = getattr(build_opts, key) val = getattr(build_opts, key)
if val is not None: if val is not None:
opts[key] = val 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("--nowait", action="store_false", dest="wait", help="Don't wait on build")
parser.add_option("--background", action="store_true", parser.add_option("--background", action="store_true",
help="Run the build at a lower priority") 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) (build_opts, args) = parser.parse_args(args)
if build_opts.inis: if build_opts.inis:
@ -839,6 +845,8 @@ def handle_wrapper_rpm(options, session, args):
if len(args) < 3: if len(args) < 3:
parser.error("You must provide a build target, a build ID or NVR, " parser.error("You must provide a build target, a build ID or NVR, "
"and a SCM URL to a specfile fragment") "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) activate_session(session, options)
target = args[0] target = args[0]
@ -874,6 +882,8 @@ def handle_wrapper_rpm(options, session, args):
opts['skip_tag'] = True opts['skip_tag'] = True
if build_opts.scratch: if build_opts.scratch:
opts['scratch'] = True opts['scratch'] = True
if build_opts.draft:
opts['draft'] = True
task_id = session.wrapperRPM(build_id, url, target, priority, opts=opts) task_id = session.wrapperRPM(build_id, url, target, priority, opts=opts)
print("Created task: %d" % task_id) print("Created task: %d" % task_id)
print("Task info: %s/taskinfo?taskID=%s" % (options.weburl, 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("--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("--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("--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) (options, args) = parser.parse_args(args)
if len(args) < 1: if len(args) < 1:
parser.error("At least one package must be specified") 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) options.src_epoch = int(options.src_epoch)
except (ValueError, TypeError): except (ValueError, TypeError):
parser.error("Invalid value for epoch: %s" % options.src_epoch) 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) activate_session(session, goptions)
to_import = {} to_import = {}
for path in args: for path in args:
@ -1335,6 +1378,7 @@ def handle_import(goptions, session, args):
else: else:
nvr = "%(name)s-%(version)s-%(release)s" % koji.parse_NVRA(data['sourcerpm']) nvr = "%(name)s-%(version)s-%(release)s" % koji.parse_NVRA(data['sourcerpm'])
to_import.setdefault(nvr, []).append((path, data)) to_import.setdefault(nvr, []).append((path, data))
builds_missing = False builds_missing = False
nvrs = sorted(to_list(to_import.keys())) nvrs = sorted(to_list(to_import.keys()))
for nvr in nvrs: for nvr in nvrs:
@ -1343,29 +1387,41 @@ def handle_import(goptions, session, args):
if data['sourcepackage']: if data['sourcepackage']:
break break
else: 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 # no srpm included, check for build
binfo = session.getBuild(nvr) binfo = session.getBuild(nvr)
if not binfo: if not binfo:
print("Missing build or srpm: %s" % nvr) print("Missing build or srpm: %s" % nvr)
builds_missing = True 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") print("Aborting import")
return return
# local function to help us out below # 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')]) rinfo = dict([(k, data[k]) for k in ('name', 'version', 'release', 'arch')])
prev = session.getRPM(rinfo) if draft_build or not options.create_draft:
if prev and not prev.get('external_repo_id', 0): opts = {}
if prev['payloadhash'] == koji.hex_string(data['sigmd5']): if draft_build:
print("RPM already imported: %s" % path) opts['build'] = draft_build
else: prev = session.getRPM(rinfo, **opts)
warn("md5sum mismatch for %s" % path) if prev and not prev.get('external_repo_id', 0):
warn(" A different rpm with the same name has already been imported") if prev['payloadhash'] == koji.hex_string(data['sigmd5']):
warn(" Existing sigmd5 is %r, your import has %r" % ( print("RPM already imported: %s" % path)
prev['payloadhash'], koji.hex_string(data['sigmd5']))) else:
print("Skipping import") warn("md5sum mismatch for %s" % path)
return 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: if options.test:
print("Test mode -- skipping import for %s" % path) print("Test mode -- skipping import for %s" % path)
return return
@ -1381,41 +1437,69 @@ def handle_import(goptions, session, args):
sys.stdout.write("importing %s... " % path) sys.stdout.write("importing %s... " % path)
sys.stdout.flush() sys.stdout.flush()
try: 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: except koji.GenericError as e:
rpminfo = None
print("\nError importing: %s" % str(e).splitlines()[-1]) print("\nError importing: %s" % str(e).splitlines()[-1])
sys.stdout.flush() sys.stdout.flush()
else: else:
print("done") print("done")
sys.stdout.flush() sys.stdout.flush()
return rpminfo
for nvr in nvrs: for nvr in nvrs:
# check for existing build # check for existing build
need_build = True need_build = True
binfo = session.getBuild(nvr) is_draft = True
if binfo: if nvr == draft_target_nvr:
b_state = koji.BUILD_STATES[binfo['state']] binfo = draft_build
if b_state == 'COMPLETE': need_build = False
need_build = False elif options.create_draft:
elif b_state in ['FAILED', 'CANCELED']: binfo = None
if not options.create_build: need_build = True
print("Build %s state is %s. Skipping import" % (nvr, b_state)) 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 continue
else:
print("Build %s exists with state=%s. Skipping import" % (nvr, b_state))
continue
# import srpms first, if any # import srpms first, if any
for path, data in to_import[nvr]: for path, data in to_import[nvr]:
if data['sourcepackage']: 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 # need to fix the state
print("Creating empty build: %s" % nvr) print("Creating empty build: %s" % nvr)
b_data = koji.util.dslice(binfo, ['name', 'version', 'release']) b_data = koji.util.dslice(binfo, ['name', 'version', 'release'])
b_data['epoch'] = data['epoch'] b_data['epoch'] = data['epoch']
session.createEmptyBuild(**b_data) session.createEmptyBuild(**b_data)
binfo = session.getBuild(nvr) 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 need_build = False
if need_build: if need_build:
@ -1424,11 +1508,11 @@ def handle_import(goptions, session, args):
if binfo: if binfo:
# should have caught this earlier, but just in case... # should have caught this earlier, but just in case...
b_state = koji.BUILD_STATES[binfo['state']] 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 continue
else: else:
print("No such build: %s (include matching srpm or use " 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 continue
else: else:
# let's make a new build # let's make a new build
@ -1439,17 +1523,22 @@ def handle_import(goptions, session, args):
# pull epoch from first rpm # pull epoch from first rpm
data = to_import[nvr][0][1] data = to_import[nvr][0][1]
b_data['epoch'] = data['epoch'] b_data['epoch'] = data['epoch']
if options.test: if options.create_draft:
print("Test mode -- would have created empty build: %s" % nvr) b_data['draft'] = True
b_display = "empty draft build with target nvr: %s" % nvr
else: else:
print("Creating empty build: %s" % nvr) b_display = "empty build: %s" % nvr
session.createEmptyBuild(**b_data) if options.test:
binfo = session.getBuild(nvr) 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]: for path, data in to_import[nvr]:
if data['sourcepackage']: if data['sourcepackage']:
continue continue
do_import(path, data) do_import(path, data, draft_build=binfo if is_draft else None)
def handle_import_cg(goptions, session, args): 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", parser.add_option("--ts", type='int', metavar="TIMESTAMP",
help="query at last event before 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("--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) (options, args) = parser.parse_args(args)
if len(args) == 0: if len(args) == 0:
parser.error("A tag name must be specified") parser.error("A tag name must be specified")
elif len(args) > 2: elif len(args) > 2:
parser.error("Only one package name may be specified") 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) ensure_connection(session, goptions)
pathinfo = koji.PathInfo() pathinfo = koji.PathInfo()
package = None package = None
@ -2753,6 +2846,10 @@ def anon_handle_list_tagged(goptions, session, args):
options.rpms = True options.rpms = True
if options.type: if options.type:
opts['type'] = 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 = koji.util.eventFromOpts(session, options)
event_id = None event_id = None
if event: if event:
@ -2798,7 +2895,9 @@ def anon_handle_list_tagged(goptions, session, args):
fmt = "%(path)s" fmt = "%(path)s"
data = [x for x in data if 'path' in x] data = [x for x in data if 'path' in x]
else: 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: if options.sigs:
fmt = "%(sigkey)s " + fmt fmt = "%(sigkey)s " + fmt
else: else:
@ -2861,10 +2960,13 @@ def anon_handle_list_buildroot(goptions, session, args):
fmt = "%(nvr)s.%(arch)s" fmt = "%(nvr)s.%(arch)s"
order = sorted([(fmt % x, x) for x in list_rpms]) order = sorted([(fmt % x, x) for x in list_rpms])
for nvra, rinfo in order: for nvra, rinfo in order:
if options.verbose and rinfo.get('is_update'): line = nvra
print("%s [update]" % nvra) if options.verbose:
else: if rinfo.get('draft'):
print(nvra) line += " (#draft_%s)" % rinfo['build_id']
if rinfo.get('is_update'):
line += " [update]"
print(line)
list_archives = session.listArchives(**opts) list_archives = session.listArchives(**opts)
if list_archives: 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("--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("--owner", help="List builds built by this owner")
parser.add_option("--volume", help="List builds by volume ID") 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', parser.add_option("-k", "--sort-key", action="append", metavar='FIELD',
default=[], help="Sort the list by the named field. Allowed sort keys: " default=[], help="Sort the list by the named field. Allowed sort keys: "
"build_id, owner_name, state") "build_id, owner_name, state")
@ -3419,6 +3523,12 @@ def anon_handle_list_builds(goptions, session, args):
value = getattr(options, key) value = getattr(options, key)
if value is not None: if value is not None:
opts[key] = value 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: if options.cg:
opts['cgID'] = options.cg opts['cgID'] = options.cg
if options.package: if options.package:
@ -3520,13 +3630,24 @@ def anon_handle_rpminfo(goptions, session, args):
parser = OptionParser(usage=get_usage_str(usage)) parser = OptionParser(usage=get_usage_str(usage))
parser.add_option("--buildroots", action="store_true", parser.add_option("--buildroots", action="store_true",
help="show buildroots the rpm was used in") 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) (options, args) = parser.parse_args(args)
if len(args) < 1: if len(args) < 1:
parser.error("Please specify an RPM") 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) ensure_connection(session, goptions)
error_hit = False error_hit = False
for rpm in args: for rpm in args:
info = session.getRPM(rpm) info = session.getRPM(rpm, **opts)
if info is None: if info is None:
warn("No such rpm: %s\n" % rpm) warn("No such rpm: %s\n" % rpm)
error_hit = True error_hit = True
@ -3536,24 +3657,29 @@ def anon_handle_rpminfo(goptions, session, args):
else: else:
info['epoch'] = str(info['epoch']) + ":" info['epoch'] = str(info['epoch']) + ":"
if not info.get('external_repo_id', 0): if not info.get('external_repo_id', 0):
buildinfo = session.getBuild(info['build_id']) if info['arch'] == 'src':
buildinfo['name'] = buildinfo['package_name'] srpminfo = info.copy()
buildinfo['arch'] = 'src'
if buildinfo['epoch'] is None:
buildinfo['epoch'] = ""
else: 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) 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'): if info.get('external_repo_id'):
repo = session.getExternalRepo(info['external_repo_id']) repo = session.getExternalRepo(info['external_repo_id'])
print("External Repository: %(name)s [%(id)i]" % repo) print("External Repository: %(name)s [%(id)i]" % repo)
print("External Repository url: %(url)s" % repo) print("External Repository url: %(url)s" % repo)
else: else:
print("Build: %(nvr)s [%(id)d]" % buildinfo)
print("RPM Path: %s" % print("RPM Path: %s" %
os.path.join(koji.pathinfo.build(buildinfo), koji.pathinfo.rpm(info))) 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" % 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', print("Built: %s" % time.strftime('%a, %d %b %Y %H:%M:%S %Z',
time.localtime(info['buildtime']))) time.localtime(info['buildtime'])))
print("SIGMD5: %(payloadhash)s" % info) print("SIGMD5: %(payloadhash)s" % info)
@ -3563,6 +3689,7 @@ def anon_handle_rpminfo(goptions, session, args):
headers=["license"]) headers=["license"])
if 'license' in headers: if 'license' in headers:
print("License: %(license)s" % headers) print("License: %(license)s" % headers)
# kept for backward compatibility
print("Build ID: %(build_id)s" % info) print("Build ID: %(build_id)s" % info)
if info['buildroot_id'] is None: if info['buildroot_id'] is None:
print("No buildroot data available") print("No buildroot data available")
@ -3619,6 +3746,8 @@ def anon_handle_buildinfo(goptions, session, args):
info['arch'] = 'src' info['arch'] = 'src'
info['state'] = koji.BUILD_STATES[info['state']] info['state'] = koji.BUILD_STATES[info['state']]
print("BUILD: %(name)s-%(version)s-%(release)s [%(id)d]" % info) print("BUILD: %(name)s-%(version)s-%(release)s [%(id)d]" % info)
if info.get('draft'):
print("Draft: YES")
print("State: %(state)s" % info) print("State: %(state)s" % info)
if info['state'] == 'BUILDING': if info['state'] == 'BUILDING':
print("Reserved by: %(cg_name)s" % info) print("Reserved by: %(cg_name)s" % info)

View file

@ -925,3 +925,10 @@ class DatetimeJSONEncoder(json.JSONEncoder):
if isinstance(o, xmlrpc_client.DateTime): if isinstance(o, xmlrpc_client.DateTime):
return str(o) return str(o)
return json.JSONEncoder.default(self, 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 base64
import datetime import datetime
import errno import errno
import functools
import hashlib import hashlib
import json import json
import logging import logging
@ -274,6 +275,7 @@ TAG_UPDATE_TYPES = Enum((
'VOLUME_CHANGE', 'VOLUME_CHANGE',
'IMPORT', 'IMPORT',
'MANUAL', 'MANUAL',
'DRAFT_PROMOTION',
)) ))
# BEGIN kojikamid dup # # BEGIN kojikamid dup #
@ -295,6 +297,44 @@ PRIO_DEFAULT = 20
DEFAULT_REQUEST_TIMEOUT = 60 * 60 * 12 DEFAULT_REQUEST_TIMEOUT = 60 * 60 * 12
DEFAULT_AUTH_TIMEOUT = 60 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 # # BEGIN kojikamid dup #
# Exceptions # Exceptions
@ -2363,6 +2403,39 @@ class PathInfo(object):
return self.volumedir(build.get('volume_name')) + \ return self.volumedir(build.get('volume_name')) + \
("/packages/%(name)s/%(version)s/%(release)s" % build) ("/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): def mavenbuild(self, build):
"""Return the directory where the Maven build exists in the global store """Return the directory where the Maven build exists in the global store
(/mnt/koji/packages)""" (/mnt/koji/packages)"""
@ -2410,6 +2483,27 @@ class PathInfo(object):
"""Return the path (relative to build_dir) where an rpm belongs""" """Return the path (relative to build_dir) where an rpm belongs"""
return "%(arch)s/%(name)s-%(version)s-%(release)s.%(arch)s.rpm" % rpminfo 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): def signed(self, rpminfo, sigkey):
"""Return the path (relative to build dir) where a signed rpm lives""" """Return the path (relative to build dir) where a signed rpm lives"""
return "data/signed/%s/" % sigkey + self.rpm(rpminfo) return "data/signed/%s/" % sigkey + self.rpm(rpminfo)

View file

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

File diff suppressed because it is too large Load diff

View file

@ -610,6 +610,11 @@ _default_policies = {
'priority': ''' 'priority': '''
all :: stay 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) 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): def _send_msgs(urls, msgs, CONFIG):
random.shuffle(urls) random.shuffle(urls)
for url in urls: for url in urls:

View file

@ -2,6 +2,7 @@
-- from version 1.33 to 1.34 -- from version 1.33 to 1.34
BEGIN; BEGIN;
-- scheduler tables -- scheduler tables
CREATE TABLE scheduler_task_runs ( CREATE TABLE scheduler_task_runs (
id SERIAL NOT NULL PRIMARY KEY, id SERIAL NOT NULL PRIMARY KEY,
@ -48,4 +49,27 @@ BEGIN;
) WITHOUT OIDS; ) WITHOUT OIDS;
INSERT INTO locks(name) VALUES('scheduler'); 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; 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 ('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-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 ('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 ( CREATE TABLE user_perms (
user_id INTEGER NOT NULL REFERENCES users(id), user_id INTEGER NOT NULL REFERENCES users(id),
@ -279,11 +280,12 @@ CREATE TABLE content_generator (
-- null, or may point to a deleted task. -- null, or may point to a deleted task.
CREATE TABLE build ( CREATE TABLE build (
id SERIAL NOT NULL PRIMARY KEY, 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, pkg_id INTEGER NOT NULL REFERENCES package (id) DEFERRABLE,
version TEXT NOT NULL, version TEXT NOT NULL,
release TEXT NOT NULL, release TEXT NOT NULL,
epoch INTEGER, epoch INTEGER,
draft BOOLEAN NOT NULL DEFAULT 'false',
source TEXT, source TEXT,
create_event INTEGER NOT NULL REFERENCES events(id) DEFAULT get_event(), create_event INTEGER NOT NULL REFERENCES events(id) DEFAULT get_event(),
start_time TIMESTAMPTZ, start_time TIMESTAMPTZ,
@ -294,8 +296,11 @@ CREATE TABLE build (
cg_id INTEGER REFERENCES content_generator(id), cg_id INTEGER REFERENCES content_generator(id),
extra TEXT, extra TEXT,
CONSTRAINT build_pkg_ver_rel UNIQUE (pkg_id, version, release), 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 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; ) WITHOUT OIDS;
CREATE INDEX build_by_pkg_id ON build (pkg_id); 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 -- we don't store filename b/c filename should be N-V-R.A.rpm
CREATE TABLE rpminfo ( CREATE TABLE rpminfo (
id SERIAL NOT NULL PRIMARY KEY, id SERIAL NOT NULL PRIMARY KEY,
build_id INTEGER REFERENCES build (id), build_id INTEGER,
buildroot_id INTEGER REFERENCES buildroot (id), buildroot_id INTEGER REFERENCES buildroot (id),
name TEXT NOT NULL, name TEXT NOT NULL,
version TEXT NOT NULL, version TEXT NOT NULL,
release TEXT NOT NULL, release TEXT NOT NULL,
epoch INTEGER, epoch INTEGER,
arch VARCHAR(16) NOT NULL, arch VARCHAR(16) NOT NULL,
draft BOOLEAN,
external_repo_id INTEGER NOT NULL REFERENCES external_repo(id), external_repo_id INTEGER NOT NULL REFERENCES external_repo(id),
payloadhash TEXT NOT NULL, payloadhash TEXT NOT NULL,
size BIGINT NOT NULL, size BIGINT NOT NULL,
buildtime BIGINT NOT NULL, buildtime BIGINT NOT NULL,
metadata_only BOOLEAN NOT NULL DEFAULT FALSE, metadata_only BOOLEAN NOT NULL DEFAULT FALSE,
extra TEXT, 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; ) WITHOUT OIDS;
CREATE INDEX rpminfo_build ON rpminfo(build_id); 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 -- index for default search method for rpms, PG11+ can benefit from new include method
DO $$ DO $$
DECLARE version integer; DECLARE version integer;

View file

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

View file

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

View file

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

View file

@ -69,6 +69,37 @@ Volume: DEFAULT
Task: 8 build (target, src) Task: 8 build (target, src)
Finished: Thu, 04 Mar 2021 14:45:40 UTC Finished: Thu, 04 Mar 2021 14:45:40 UTC
Tags: 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]) anon_handle_buildinfo(self.options, self.session, [build])
self.assert_console_message(stdout, expected_stdout) 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): def __do_import_test(self, options, session, arguments, **kwargs):
expected = kwargs.get('expected', None) expected = kwargs.get('expected', None)
rpm_header = kwargs.get('rpm_header', {}) 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') fake_srv_path = kwargs.get('srv_path', '/path/to/server/import')
upload_rpm_mock = kwargs.get('upload_rpm_mock', session.uploadWrapper) 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.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.unique_path') as unique_path_mock:
with mock.patch('koji_cli.commands.activate_session') as activate_session_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 mock.patch('sys.stdout', new_callable=six.StringIO) as stdout:
with upload_rpm_mock: 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 unique_path_mock.return_value = fake_srv_path
handle_import(options, session, arguments) handle_import(options, session, arguments)
@ -104,21 +111,33 @@ class TestImport(utils.CliTestCase):
# check mock calls # check mock calls
activate_session_mock.assert_called_with(session, options) activate_session_mock.assert_called_with(session, options)
get_header_fields_mock.assert_called_with(
arguments[0], get_header_fields_calls = [
('name', 'version', 'release', 'epoch', mock.call(arguments[i],
'arch', 'sigmd5', 'sourcepackage', 'sourcerpm') ('name', 'version', 'release', 'epoch',
) 'arch', 'sigmd5', 'sourcepackage', 'sourcerpm')
) for i in range(len(rpm_headers) - 1)
]
session.getRPM.assert_called_with( get_header_fields_mock.assert_has_calls(get_header_fields_calls)
dict((k, rpm_header.get(k, ''))
for k in ['release', 'version', 'arch', 'name']) 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') unique_path_mock.assert_called_with('cli-import')
upload_rpm_mock.assert_called_with(arguments[0], self.fake_srv_dir) upload_rpm_mock.assert_called_with(arguments[0], self.fake_srv_dir)
session.importRPM.assert_called_with( if import_rpm_calls:
self.fake_srv_dir, os.path.basename(arguments[0])) 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 # reset for next test
activate_session_mock.reset_mock() activate_session_mock.reset_mock()
@ -136,6 +155,7 @@ class TestImport(utils.CliTestCase):
expected = kwargs.get('expected', None) expected = kwargs.get('expected', None)
expected_warn = kwargs.get('expected_warn', None) expected_warn = kwargs.get('expected_warn', None)
rpm_header = kwargs.get('rpm_header', {}) 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: with mock.patch('koji.get_header_fields') as get_header_fields_mock:
get_header_fields_mock.return_value = rpm_header get_header_fields_mock.return_value = rpm_header
@ -152,11 +172,13 @@ class TestImport(utils.CliTestCase):
('name', 'version', 'release', 'epoch', ('name', 'version', 'release', 'epoch',
'arch', 'sigmd5', 'sourcepackage', 'sourcerpm') 'arch', 'sigmd5', 'sourcepackage', 'sourcerpm')
) )
if getrpm_called:
session.getRPM.assert_called_with( session.getRPM.assert_called_with(
dict((k, rpm_header.get(k, '')) dict((k, rpm_header.get(k, ''))
for k in ['release', 'version', 'arch', 'name']) for k in ['release', 'version', 'arch', 'name'])
) )
else:
session.getRPM.assert_not_called()
session.uploadWrapper.assert_not_called() session.uploadWrapper.assert_not_called()
session.importRPM.assert_not_called() session.importRPM.assert_not_called()
@ -677,6 +699,225 @@ class TestImport(utils.CliTestCase):
activate_session=None) activate_session=None)
activate_session_mock.assert_not_called() 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): def test_handle_import_help(self):
"""Test handle_import function help message""" """Test handle_import function help message"""
self.assert_help( self.assert_help(
@ -691,6 +932,8 @@ Options:
--create-build Auto-create builds as needed --create-build Auto-create builds as needed
--src-epoch=SRC_EPOCH --src-epoch=SRC_EPOCH
When auto-creating builds, use this 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) """ % self.progname)

View file

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

View file

@ -42,6 +42,15 @@ class TestCliListTagged(utils.CliTestCase):
'release': '1.el6', 'release': '1.el6',
'arch': 'x86_64', 'arch': 'x86_64',
'sigkey': 'sigkey', '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} 'extra': None}
], [{'id': 1, ], [{'id': 1,
'name': 'packagename', 'name': 'packagename',
@ -50,6 +59,15 @@ class TestCliListTagged(utils.CliTestCase):
'nvr': 'n-v-r', 'nvr': 'n-v-r',
'tag_name': 'tag', 'tag_name': 'tag',
'owner_name': 'owner', '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'}]] 'extra': 'extra-value-2'}]]
self.session.listTagged.return_value = [{'id': 1, self.session.listTagged.return_value = [{'id': 1,
'name': 'packagename', 'name': 'packagename',
@ -77,13 +95,15 @@ Build Tag Built by
---------------------------------------- -------------------- ---------------- ---------------------------------------- -------------------- ----------------
n-v-r tag owner 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) anon_handle_list_tagged(self.options, self.session, args)
self.ensure_connection_mock.assert_called_once_with(self.session, self.options) 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.getTag.assert_called_once_with(self.tag, event=self.event_id)
self.session.listTagged.assert_called_once_with( 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.session.listTaggedRPMS.assert_not_called()
self.assert_console_message(stdout, expected) self.assert_console_message(stdout, expected)
@ -94,14 +114,14 @@ n-v-r tag owner
---------------------------------------- -------------------- ---------------- ---------------------------------------- -------------------- ----------------
/mnt/koji/packages/packagename/version/1.el6 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) anon_handle_list_tagged(self.options, self.session, args)
self.assert_console_message(stdout, expected) self.assert_console_message(stdout, expected)
self.ensure_connection_mock.assert_called_once_with(self.session, self.options) 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.getTag.assert_called_once_with(self.tag, event=None)
self.session.listTagged.assert_called_once_with( 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() self.session.listTaggedRPMS.assert_not_called()
@mock.patch('sys.stdout', new_callable=six.StringIO) @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): def test_list_tagged_rpms(self, event_from_opts_mock, stdout):
expected = """sigkey rpmA-0.0.1-1.el6.noarch expected = """sigkey rpmA-0.0.1-1.el6.noarch
sigkey rpmA-0.0.1-1.el6.x86_64 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', args = [self.tag, self.pkg, '--latest-n=3', '--rpms', '--sigs',
'--arch=x86_64', '--arch=noarch'] '--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): 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 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/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'] 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.listTaggedRPMS.assert_not_called()
self.session.listTagged.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): def test_list_tagged_tag_not_found(self):
self.session.getTag.return_value = None self.session.getTag.return_value = None
self.assert_system_exit( self.assert_system_exit(
@ -267,4 +298,6 @@ Options:
--event=EVENT# query at event --event=EVENT# query at event
--ts=TIMESTAMP query at last event before timestamp --ts=TIMESTAMP query at last event before timestamp
--repo=REPO# query at event for a repo --repo=REPO# query at event for a repo
--draft-only Only list draft builds/rpms
--no-draft Only list regular builds/rpms
""" % self.progname) """ % self.progname)

View file

@ -53,6 +53,17 @@ class TestRpminfo(utils.CliTestCase):
'version': '1.1', 'version': '1.1',
'payloadhash': 'b2b95550390e5f213fc25f33822425f7', 'payloadhash': 'b2b95550390e5f213fc25f33822425f7',
'size': 7030} '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> ...] 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) (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.listBuildroots.return_value = [self.buildroot_info]
self.session.getBuild.return_value = self.buildinfo self.session.getBuild.return_value = self.buildinfo
self.session.getRPM.return_value = self.getrpminfo 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] 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 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 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 Built: Tue, 16 Mar 2021 06:56:49 UTC
SIGMD5: b2b95550390e5f213fc25f33822425f7 SIGMD5: b2b95550390e5f213fc25f33822425f7
@ -98,6 +111,8 @@ Used in 1 buildroots:
rpmID=self.getrpminfo['id']) rpmID=self.getrpminfo['id'])
self.session.getBuild.assert_called_once_with(self.getrpminfo['build_id']) self.session.getBuild.assert_called_once_with(self.getrpminfo['build_id'])
self.session.getRPM.assert_called_once_with(rpm_nvra) 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): def test_handle_rpminfo_non_exist_nvra(self):
rpm_nvra = 'test-rpm-nvra.arch' rpm_nvra = 'test-rpm-nvra.arch'
@ -119,9 +134,11 @@ Used in 1 buildroots:
self.session.listBuildroots.return_value = [self.buildroot_info] self.session.listBuildroots.return_value = [self.buildroot_info]
self.session.getBuild.return_value = self.buildinfo self.session.getBuild.return_value = self.buildinfo
self.session.getRPM.side_effect = [None, self.getrpminfo] 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] 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 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 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 Built: Tue, 16 Mar 2021 06:56:49 UTC
SIGMD5: b2b95550390e5f213fc25f33822425f7 SIGMD5: b2b95550390e5f213fc25f33822425f7
@ -150,6 +167,45 @@ Used in 1 buildroots:
rpmID=self.getrpminfo['id']) rpmID=self.getrpminfo['id'])
self.session.getBuild.assert_called_once_with(self.getrpminfo['build_id']) self.session.getBuild.assert_called_once_with(self.getrpminfo['build_id'])
self.assertEqual(self.session.getRPM.call_count, 2) 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): def test_rpminfo_without_option(self):
arguments = [] arguments = []
@ -171,6 +227,7 @@ Used in 1 buildroots:
(Specify the --help global option for a list of other help options) (Specify the --help global option for a list of other help options)
Options: Options:
-h, --help show this help message and exit -h, --help show this help message and exit
--buildroots show buildroots the rpm was used in --buildroots show buildroots the rpm was used in
--build=NVR|ID show the rpm(s) in the build
""" % self.progname) """ % self.progname)

View file

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

View file

@ -16,6 +16,10 @@ class TestGetRPM(DBQueryTestCase):
self.exports = kojihub.RootExports() self.exports = kojihub.RootExports()
self.context = mock.patch('kojihub.kojihub.context').start() self.context = mock.patch('kojihub.kojihub.context').start()
self.get_external_repo_id = mock.patch('kojihub.kojihub.get_external_repo_id').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): def test_wrong_type_rpminfo(self):
rpminfo = ['test-user'] rpminfo = ['test-user']
@ -31,11 +35,10 @@ class TestGetRPM(DBQueryTestCase):
self.assertEqual(len(self.queries), 1) self.assertEqual(len(self.queries), 1)
query = self.queries[0] query = self.queries[0]
str(query)
self.assertEqual(query.tables, ['rpminfo']) self.assertEqual(query.tables, ['rpminfo'])
columns = ['rpminfo.id', 'build_id', 'buildroot_id', 'rpminfo.name', 'version', 'release', columns = ['rpminfo.id', 'build_id', 'buildroot_id', 'rpminfo.name', 'version', 'release',
'epoch', 'arch', 'external_repo_id', 'external_repo.name', 'payloadhash', 'epoch', 'arch', 'draft', 'external_repo_id', 'external_repo.name',
'size', 'buildtime', 'metadata_only', 'extra'] 'payloadhash', 'size', 'buildtime', 'metadata_only', 'extra']
self.assertEqual(set(query.columns), set(columns)) self.assertEqual(set(query.columns), set(columns))
self.assertEqual(query.clauses, ['external_repo_id = 0', "rpminfo.id=%(id)s"]) self.assertEqual(query.clauses, ['external_repo_id = 0', "rpminfo.id=%(id)s"])
self.assertEqual(query.joins, self.assertEqual(query.joins,
@ -50,11 +53,10 @@ class TestGetRPM(DBQueryTestCase):
self.assertEqual(len(self.queries), 1) self.assertEqual(len(self.queries), 1)
query = self.queries[0] query = self.queries[0]
str(query)
self.assertEqual(query.tables, ['rpminfo']) self.assertEqual(query.tables, ['rpminfo'])
columns = ['rpminfo.id', 'build_id', 'buildroot_id', 'rpminfo.name', 'version', 'release', columns = ['rpminfo.id', 'build_id', 'buildroot_id', 'rpminfo.name', 'version', 'release',
'epoch', 'arch', 'external_repo_id', 'external_repo.name', 'payloadhash', 'epoch', 'arch', 'draft', 'external_repo_id', 'external_repo.name',
'size', 'buildtime', 'metadata_only', 'extra'] 'payloadhash', 'size', 'buildtime', 'metadata_only', 'extra']
self.assertEqual(set(query.columns), set(columns)) self.assertEqual(set(query.columns), set(columns))
self.assertEqual(query.clauses, ["rpminfo.id=%(id)s"]) self.assertEqual(query.clauses, ["rpminfo.id=%(id)s"])
self.assertEqual(query.joins, self.assertEqual(query.joins,
@ -70,11 +72,10 @@ class TestGetRPM(DBQueryTestCase):
self.assertEqual(len(self.queries), 1) self.assertEqual(len(self.queries), 1)
query = self.queries[0] query = self.queries[0]
str(query)
self.assertEqual(query.tables, ['rpminfo']) self.assertEqual(query.tables, ['rpminfo'])
columns = ['rpminfo.id', 'build_id', 'buildroot_id', 'rpminfo.name', 'version', 'release', columns = ['rpminfo.id', 'build_id', 'buildroot_id', 'rpminfo.name', 'version', 'release',
'epoch', 'arch', 'external_repo_id', 'external_repo.name', 'payloadhash', 'epoch', 'arch', 'draft', 'external_repo_id', 'external_repo.name',
'size', 'buildtime', 'metadata_only', 'extra'] 'payloadhash', 'size', 'buildtime', 'metadata_only', 'extra']
self.assertEqual(set(query.columns), set(columns)) self.assertEqual(set(query.columns), set(columns))
self.assertEqual(query.clauses, ["rpminfo.id=%(id)s"]) self.assertEqual(query.clauses, ["rpminfo.id=%(id)s"])
self.assertEqual(query.joins, self.assertEqual(query.joins,
@ -87,11 +88,10 @@ class TestGetRPM(DBQueryTestCase):
self.assertEqual(len(self.queries), 1) self.assertEqual(len(self.queries), 1)
query = self.queries[0] query = self.queries[0]
str(query)
self.assertEqual(query.tables, ['rpminfo']) self.assertEqual(query.tables, ['rpminfo'])
columns = ['rpminfo.id', 'build_id', 'buildroot_id', 'rpminfo.name', 'version', 'release', columns = ['rpminfo.id', 'build_id', 'buildroot_id', 'rpminfo.name', 'version', 'release',
'epoch', 'arch', 'external_repo_id', 'external_repo.name', 'payloadhash', 'epoch', 'arch', 'draft', 'external_repo_id', 'external_repo.name',
'size', 'buildtime', 'metadata_only', 'extra'] 'payloadhash', 'size', 'buildtime', 'metadata_only', 'extra']
self.assertEqual(set(query.columns), set(columns)) self.assertEqual(set(query.columns), set(columns))
self.assertEqual(query.clauses, ["rpminfo.name=%(name)s AND version=%(version)s " self.assertEqual(query.clauses, ["rpminfo.name=%(name)s AND version=%(version)s "
"AND release=%(release)s AND arch=%(arch)s"]) "AND release=%(release)s AND arch=%(arch)s"])
@ -110,17 +110,36 @@ class TestGetRPM(DBQueryTestCase):
self.assertEqual(len(self.queries), 1) self.assertEqual(len(self.queries), 1)
query = self.queries[0] query = self.queries[0]
str(query)
self.assertEqual(query.tables, ['rpminfo']) self.assertEqual(query.tables, ['rpminfo'])
columns = ['rpminfo.id', 'build_id', 'buildroot_id', 'rpminfo.name', 'version', 'release', columns = ['rpminfo.id', 'build_id', 'buildroot_id', 'rpminfo.name', 'version', 'release',
'epoch', 'arch', 'external_repo_id', 'external_repo.name', 'payloadhash', 'epoch', 'arch', 'draft', 'external_repo_id', 'external_repo.name',
'size', 'buildtime', 'metadata_only', 'extra'] 'payloadhash', 'size', 'buildtime', 'metadata_only', 'extra']
self.assertEqual(set(query.columns), set(columns)) self.assertEqual(set(query.columns), set(columns))
self.assertEqual(query.clauses, self.assertEqual(query.clauses,
["external_repo_id = %(external_repo_id)i", "rpminfo.id=%(id)s"]) ["external_repo_id = %(external_repo_id)i", "rpminfo.id=%(id)s"])
self.assertEqual(query.joins, self.assertEqual(query.joins,
['external_repo ON rpminfo.external_repo_id = external_repo.id']) ['external_repo ON rpminfo.external_repo_id = external_repo.id'])
self.assertEqual(query.values, rpminfo_data) 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): class TestGetRPMHeaders(unittest.TestCase):

View file

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

View file

@ -22,7 +22,8 @@ class TestGetNextRelease(DBQueryTestCase):
self.assertEqual(query.tables, ['build']) self.assertEqual(query.tables, ['build'])
self.assertEqual(query.joins, ['package ON build.pkg_id = package.id']) self.assertEqual(query.joins, ['package ON build.pkg_id = package.id'])
self.assertEqual(query.clauses, 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'], self.assertEqual(query.values, {'name': self.binfo['name'],
'version': self.binfo['version'], 'version': self.binfo['version'],
'states': (1, 2, 0) 'states': (1, 2, 0)

View file

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

View file

@ -42,11 +42,12 @@ class TestImportRPM(unittest.TestCase):
1003: 'epoch', 1003: 'epoch',
1006: 'buildtime', 1006: 'buildtime',
1022: 'arch', 1022: 'arch',
1044: 'name-version-release.arch', 1044: 'name-version-release.src.rpm',
1106: 'sourcepackage', 1106: 'sourcepackage',
261: 'payload hash', 261: 'payload hash',
} }
self.get_build = mock.patch('kojihub.kojihub.get_build').start() 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.get_rpm_header = mock.patch('koji.get_rpm_header').start()
self.new_typed_build = mock.patch('kojihub.kojihub.new_typed_build').start() self.new_typed_build = mock.patch('kojihub.kojihub.new_typed_build').start()
self.nextval = mock.patch('kojihub.kojihub.nextval').start() self.nextval = mock.patch('kojihub.kojihub.nextval').start()
@ -65,6 +66,7 @@ class TestImportRPM(unittest.TestCase):
kojihub.import_rpm("this does not exist") kojihub.import_rpm("this does not exist")
def test_import_rpm_failed_build(self): 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_rpm_header.return_value = self.rpm_header_retval
self.get_build.return_value = { self.get_build.return_value = {
'state': koji.BUILD_STATES['FAILED'], 'state': koji.BUILD_STATES['FAILED'],
@ -72,9 +74,11 @@ class TestImportRPM(unittest.TestCase):
'version': 'version', 'version': 'version',
'release': 'release', 'release': 'release',
} }
with self.assertRaises(koji.GenericError): with self.assertRaises(koji.GenericError) as cm:
kojihub.import_rpm(self.filename) kojihub.import_rpm(self.filename)
self.assertEqual("Build is FAILED: name-version-release", str(cm.exception))
self.assertEqual(len(self.inserts), 0) self.assertEqual(len(self.inserts), 0)
def test_import_rpm_completed_build(self): def test_import_rpm_completed_build(self):
self.os_path_basename.return_value = 'name-version-release.arch.rpm' self.os_path_basename.return_value = 'name-version-release.arch.rpm'
@ -94,6 +98,7 @@ class TestImportRPM(unittest.TestCase):
'name': 'name', 'name': 'name',
'arch': 'arch', 'arch': 'arch',
'buildtime': 'buildtime', 'buildtime': 'buildtime',
'draft': False,
'payloadhash': '7061796c6f61642068617368', 'payloadhash': '7061796c6f61642068617368',
'epoch': 'epoch', 'epoch': 'epoch',
'version': 'version', 'version': 'version',
@ -114,7 +119,7 @@ class TestImportRPM(unittest.TestCase):
retval = copy.copy(self.rpm_header_retval) retval = copy.copy(self.rpm_header_retval)
retval.update({ retval.update({
'filename': 'name-version-release.arch.rpm', 'filename': 'name-version-release.arch.rpm',
1044: 'name-version-release.src', 1044: 'name-version-release.src.rpm.bad',
1022: 'src', 1022: 'src',
1106: 1, 1106: 1,
}) })
@ -133,6 +138,7 @@ class TestImportRPM(unittest.TestCase):
'name': 'name', 'name': 'name',
'arch': 'src', 'arch': 'src',
'buildtime': 'buildtime', 'buildtime': 'buildtime',
'draft': False,
'payloadhash': '7061796c6f61642068617368', 'payloadhash': '7061796c6f61642068617368',
'epoch': 'epoch', 'epoch': 'epoch',
'version': 'version', 'version': 'version',
@ -149,10 +155,9 @@ class TestImportRPM(unittest.TestCase):
self.assertEqual(insert.rawdata, {}) self.assertEqual(insert.rawdata, {})
def test_non_exist_file(self): def test_non_exist_file(self):
basename = 'rpm-1-34'
self.os_path_exists.return_value = False self.os_path_exists.return_value = False
with self.assertRaises(koji.GenericError) as cm: 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(f"No such file: {self.filename}", str(cm.exception))
self.assertEqual(len(self.inserts), 0) self.assertEqual(len(self.inserts), 0)
@ -172,3 +177,211 @@ class TestImportRPM(unittest.TestCase):
kojihub.import_rpm(self.src_filename) kojihub.import_rpm(self.src_filename)
self.assertEqual("No such build", str(cm.exception)) self.assertEqual("No such build", str(cm.exception))
self.assertEqual(len(self.inserts), 0) 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 return query
def setUp(self): 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.maxDiff = None
self.exports = kojihub.RootExports() self.exports = kojihub.RootExports()
self.query_executeOne = mock.MagicMock() self.query_executeOne = mock.MagicMock()
@ -41,7 +67,8 @@ class TestListBuilds(unittest.TestCase):
'task_id': 879, 'task_id': 879,
'version': '11', 'version': '11',
'volume_id': 0, 'volume_id': 0,
'volume_name': 'DEFAULT'}] 'volume_name': 'DEFAULT',
'draft': False},]
def test_wrong_package(self): def test_wrong_package(self):
package = 'test-package' package = 'test-package'
@ -58,26 +85,27 @@ class TestListBuilds(unittest.TestCase):
self.assertEqual(len(self.queries), 1) self.assertEqual(len(self.queries), 1)
args, kwargs = self.QueryProcessor.call_args args, kwargs = self.QueryProcessor.call_args
qp = QP(**kwargs) qp = QP(**kwargs)
self.assertEqual(qp.tables, ['build']) self.assertEqual(qp.tables, self.tables)
self.assertEqual(qp.columns, ['build.id', 'build.completion_time', self.assertEqual(qp.columns, self.columns)
"date_part('epoch', build.completion_time)", self.assertEqual(qp.clauses, self.clauses)
'events.id', 'events.time', self.assertEqual(qp.joins, self.joins)
"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'])
def test_wrong_user(self): def test_wrong_user(self):
user = 'test-user' user = 'test-user'
self.get_user.return_value = None self.get_user.return_value = None
rv = self.exports.listBuilds(userID=user) rv = self.exports.listBuilds(userID=user)
self.assertEqual(rv, []) 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 unittest
import koji import koji
from koji.util import dslice
import kojihub import kojihub
IP = kojihub.InsertProcessor IP = kojihub.InsertProcessor
@ -23,6 +24,7 @@ class TestNewBuild(unittest.TestCase):
self.get_build = mock.patch('kojihub.kojihub.get_build').start() self.get_build = mock.patch('kojihub.kojihub.get_build').start()
self.recycle_build = mock.patch('kojihub.kojihub.recycle_build').start() self.recycle_build = mock.patch('kojihub.kojihub.recycle_build').start()
self.context = mock.patch('kojihub.kojihub.context').start() self.context = mock.patch('kojihub.kojihub.context').start()
self.find_build_id = mock.patch('kojihub.kojihub.find_build_id').start()
def tearDown(self): def tearDown(self):
mock.patch.stopall() mock.patch.stopall()
@ -64,6 +66,7 @@ class TestNewBuild(unittest.TestCase):
'start_time': 'NOW', 'start_time': 'NOW',
'state': 1, 'state': 1,
'task_id': None, 'task_id': None,
'draft': False,
'version': 'test_version', 'version': 'test_version',
'volume_id': 0 'volume_id': 0
}) })
@ -156,3 +159,48 @@ class TestNewBuild(unittest.TestCase):
self.assertEqual(len(self.inserts), 0) self.assertEqual(len(self.inserts), 0)
self.assertEqual("No such build extra data: %(extra)r" % data, str(cm.exception)) 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.tag_name = 'test-tag'
self.columns = ['tag.id', 'tag.name', 'build.id', 'build.version', 'build.release', self.columns = ['tag.id', 'tag.name', 'build.id', 'build.version', 'build.release',
'build.epoch', 'build.state', 'build.completion_time', 'build.start_time', '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', 'volume.id', 'volume.name', 'package.id', 'package.name',
'package.name || \'-\' || build.version || \'-\' || build.release', 'package.name || \'-\' || build.version || \'-\' || build.release',
'tag_listing.create_event'] 'tag_listing.create_event']
@ -40,6 +40,7 @@ class TestReadTaggedBuilds(unittest.TestCase):
('build.release', 'release'), ('build.epoch', 'epoch'), ('build.release', 'release'), ('build.epoch', 'epoch'),
('build.state', 'state'), ('build.completion_time', 'completion_time'), ('build.state', 'state'), ('build.completion_time', 'completion_time'),
('build.start_time', 'start_time'), ('build.task_id', 'task_id'), ('build.start_time', 'start_time'), ('build.task_id', 'task_id'),
('build.draft', 'draft'),
('users.id', 'owner_id'), ('users.name', 'owner_name'), ('users.id', 'owner_id'), ('users.name', 'owner_name'),
('events.id', 'creation_event_id'), ('events.time', 'creation_time'), ('events.id', 'creation_event_id'), ('events.time', 'creation_time'),
('volume.id', 'volume_id'), ('volume.name', 'volume_name'), ('volume.id', 'volume_id'), ('volume.name', 'volume_name'),
@ -54,7 +55,7 @@ class TestReadTaggedBuilds(unittest.TestCase):
'volume ON volume.id = build.volume_id', 'volume ON volume.id = build.volume_id',
'users ON users.id = build.owner', ] 'users ON users.id = build.owner', ]
self.aliases = ['tag_id', 'tag_name', 'id', 'build_id', 'version', 'release', 'epoch', 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', 'owner_name', 'creation_event_id', 'creation_time', 'volume_id',
'volume_name', 'package_id', 'package_name', 'name', 'nvr', 'create_event'] 'volume_name', 'package_id', 'package_name', 'name', 'nvr', 'create_event']
self.clauses = ['(tag_listing.active = TRUE)', self.clauses = ['(tag_listing.active = TRUE)',
@ -83,7 +84,7 @@ class TestReadTaggedBuilds(unittest.TestCase):
'package': None, 'packages': self.package_list, 'package': None, 'packages': self.package_list,
'queryOpts': {'order': '-create_event'}, 'st_complete': 1, 'tables': self.tables, 'queryOpts': {'order': '-create_event'}, 'st_complete': 1, 'tables': self.tables,
'tag': self.tag_name, 'tagid': self.tag_name, 'taglist': [self.tag_name], '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.tables, self.tables)
self.assertEqual(query.joins, self.joins) self.assertEqual(query.joins, self.joins)
@ -119,7 +120,7 @@ class TestReadTaggedBuilds(unittest.TestCase):
'package': self.pkg_name, 'packages': self.package_list, 'package': self.pkg_name, 'packages': self.package_list,
'queryOpts': {'order': '-create_event'}, 'st_complete': 1, 'tables': self.tables, 'queryOpts': {'order': '-create_event'}, 'st_complete': 1, 'tables': self.tables,
'tag': self.tag_name, 'tagid': self.tag_name, 'taglist': [self.tag_name], '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.tables, self.tables)
self.assertEqual(query.joins, joins) self.assertEqual(query.joins, joins)
self.assertEqual(set(query.columns), set(columns)) self.assertEqual(set(query.columns), set(columns))
@ -148,7 +149,7 @@ class TestReadTaggedBuilds(unittest.TestCase):
'package': None, 'packages': self.package_list, 'package': None, 'packages': self.package_list,
'queryOpts': {'order': '-create_event'}, 'st_complete': 1, 'tables': self.tables, 'queryOpts': {'order': '-create_event'}, 'st_complete': 1, 'tables': self.tables,
'tag': self.tag_name, 'tagid': self.tag_name, 'taglist': [self.tag_name], '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.tables, self.tables)
self.assertEqual(query.joins, joins) self.assertEqual(query.joins, joins)
self.assertEqual(set(query.columns), set(columns)) self.assertEqual(set(query.columns), set(columns))
@ -177,7 +178,7 @@ class TestReadTaggedBuilds(unittest.TestCase):
'package': None, 'packages': self.package_list, 'package': None, 'packages': self.package_list,
'queryOpts': {'order': '-create_event'}, 'st_complete': 1, 'tables': self.tables, 'queryOpts': {'order': '-create_event'}, 'st_complete': 1, 'tables': self.tables,
'tag': self.tag_name, 'tagid': self.tag_name, 'taglist': [self.tag_name], '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.tables, self.tables)
self.assertEqual(query.joins, joins) self.assertEqual(query.joins, joins)
self.assertEqual(set(query.columns), set(columns)) self.assertEqual(set(query.columns), set(columns))
@ -212,10 +213,35 @@ class TestReadTaggedBuilds(unittest.TestCase):
'package': None, 'packages': self.package_list, 'package': None, 'packages': self.package_list,
'queryOpts': {'order': '-create_event'}, 'st_complete': 1, 'tables': self.tables, 'queryOpts': {'order': '-create_event'}, 'st_complete': 1, 'tables': self.tables,
'tag': self.tag_name, 'tagid': self.tag_name, 'taglist': [self.tag_name], '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.tables, self.tables)
self.assertEqual(query.joins, joins) self.assertEqual(query.joins, joins)
self.assertEqual(set(query.columns), set(self.columns)) self.assertEqual(set(query.columns), set(self.columns))
self.assertEqual(set(query.aliases), set(self.aliases)) self.assertEqual(set(query.aliases), set(self.aliases))
self.assertEqual(set(query.clauses), set(self.clauses)) self.assertEqual(set(query.clauses), set(self.clauses))
self.assertEqual(query.values, values) 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.readTaggedBuilds = mock.patch('kojihub.kojihub.readTaggedBuilds').start()
self.tag_name = 'test-tag' self.tag_name = 'test-tag'
self.columns = ['rpminfo.name', 'rpminfo.version', 'rpminfo.release', 'rpminfo.arch', self.columns = ['rpminfo.name', 'rpminfo.version', 'rpminfo.release', 'rpminfo.arch',
'rpminfo.id', 'rpminfo.epoch', 'rpminfo.payloadhash', 'rpminfo.size', 'rpminfo.id', 'rpminfo.epoch', 'rpminfo.draft', 'rpminfo.payloadhash',
'rpminfo.buildtime', 'rpminfo.buildroot_id', 'rpminfo.build_id', 'rpminfo.size', 'rpminfo.buildtime', 'rpminfo.buildroot_id',
'rpminfo.metadata_only'] 'rpminfo.build_id', 'rpminfo.metadata_only']
self.joins = ['tag_listing ON rpminfo.build_id = tag_listing.build_id'] self.joins = ['tag_listing ON rpminfo.build_id = tag_listing.build_id']
self.aliases = ['name', 'version', 'release', 'arch', 'id', 'epoch', 'payloadhash', self.aliases = ['name', 'version', 'release', 'arch', 'id', 'epoch', 'draft',
'size', 'buildtime', 'buildroot_id', 'build_id', 'metadata_only'] 'payloadhash', 'size', 'buildtime', 'buildroot_id', 'build_id',
'metadata_only']
self.clauses = ['(tag_listing.active = TRUE)', self.clauses = ['(tag_listing.active = TRUE)',
'tag_id=%(tagid)s'] 'tag_id=%(tagid)s']
self.tables = ['rpminfo'] self.tables = ['rpminfo']
@ -101,3 +102,20 @@ class TestReadTaggedRPMS(unittest.TestCase):
self.assertEqual(set(query.aliases), set(aliases)) self.assertEqual(set(query.aliases), set(aliases))
self.assertEqual(set(query.clauses), set(clauses)) self.assertEqual(set(query.clauses), set(clauses))
self.assertEqual(query.values, values) 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.get_build = mock.patch('kojihub.kojihub.get_build').start()
self.context = mock.patch('kojihub.kojihub.context').start() self.context = mock.patch('kojihub.kojihub.context').start()
self.context.session.assertPerm = mock.MagicMock() 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.build_id = 3
self.binfo = {'id': 3, 'state': koji.BUILD_STATES['COMPLETE'], 'name': 'test_nvr', self.binfo = {'id': 3, 'state': koji.BUILD_STATES['COMPLETE'], 'name': 'test_nvr',
'nvr': 'test_nvr-3.3-20.el8', 'version': '3.3', 'release': '20', 'nvr': 'test_nvr-3.3-20.el8', 'version': '3.3', 'release': '20',