diff --git a/cli/koji_cli/commands.py b/cli/koji_cli/commands.py index 29b6e0a6..fe50f208 100644 --- a/cli/koji_cli/commands.py +++ b/cli/koji_cli/commands.py @@ -1474,15 +1474,25 @@ def handle_import_cg(goptions, session, args): help="Attempt to hardlink instead of uploading") parser.add_option("--test", action="store_true", help="Don't actually import") parser.add_option("--token", action="store", default=None, help="Build reservation token") + parser.add_option("--draft", action="store_true", default=False, help="Import as a draft") + parser.add_option("--build-id", action="store", type="int", default=None, + help="Reserved build id") (options, args) = parser.parse_args(args) if len(args) < 2: parser.error("Please specify metadata files directory") activate_session(session, goptions) + + # get the metadata metadata = koji.load_json(args[0]) + if options.build_id: + metadata['build']['build_id'] = options.build_id + if options.draft: + metadata['build']['draft'] = True + + # prepare output list for upload if 'output' not in metadata: error("Metadata contains no output") localdir = args[1] - to_upload = [] for info in metadata['output']: if info.get('metadata_only', False): @@ -1495,10 +1505,9 @@ def handle_import_cg(goptions, session, args): if options.test: return - # get upload path - # XXX - need a better way + # upload our outputs + # TODO - need a better way to select upload path serverdir = unique_path('cli-import') - for localpath, info in to_upload: relpath = os.path.join(serverdir, info.get('relpath', '')) if _running_in_bg() or options.noprogress: @@ -1516,6 +1525,53 @@ def handle_import_cg(goptions, session, args): session.CGImport(metadata, serverdir, options.token) +def handle_reserve_cg(goptions, session, args): + "[admin] Reserve a build entry for later import" + usage = "usage: %prog reserve-cg [options] |" + parser = OptionParser(usage=get_usage_str(usage)) + parser.add_option("--draft", action="store_true", default=False, help="Reserve a draft entry") + parser.add_option("--metadata", action="store_true", default=False, + help="Treat argument as a metadata file") + (options, args) = parser.parse_args(args) + if len(args) != 2: + parser.error("The command takes exactly two arguments") + activate_session(session, goptions) + + cg = args[0] + + def read_nvr(): + try: + data = koji.parse_NVR(args[1]) + except Exception: + return None + # fix epoch + if data['epoch'] == '': + data['epoch'] = None + else: + data['epoch'] = int(data['epoch']) + return data + + def read_metadata(): + metadata = koji.load_json(args[1]) + data = metadata['build'] + fields = ('name', 'version', 'release', 'epoch', 'draft') + data = koji.util.dslice(data, fields, strict=False) + return data + + if options.metadata: + data = read_metadata() + else: + data = read_nvr() + if not data: + data = read_metadata() + + if options.draft: + data['draft'] = True + + result = session.CGInitBuild(cg, data) + print('Reserved build %(build_id)s with token %(token)r' % result) + + def handle_import_comps(goptions, session, args): "Import group/package information from a comps file" usage = "usage: %prog import-comps [options] " diff --git a/kojihub/kojihub.py b/kojihub/kojihub.py index d645e71d..c3268545 100644 --- a/kojihub/kojihub.py +++ b/kojihub/kojihub.py @@ -6794,7 +6794,6 @@ def cg_init_build(cg, data): other values will be ignored anyway (owner, state, ...) :return: dict with build_id and token """ - reject_draft(data) assert_cg(cg) cg_id = lookup_name('content_generator', cg, strict=True)['id'] data['owner'] = context.session.user_id @@ -6994,6 +6993,7 @@ class CG_Importer(object): 'release': self.buildinfo['release'], 'btypes': list(self.typeinfo), 'source': self.buildinfo.get('source'), + 'draft': self.buildinfo.get('draft'), 'metadata_only': self.metadata_only, 'cg_list': [self.cg], # TODO: provide more data @@ -7058,9 +7058,19 @@ class CG_Importer(object): raise koji.GenericError('Build is owned by task %(task_id)s' % buildinfo) if buildinfo['state'] != koji.BUILD_STATES['BUILDING']: raise koji.GenericError('Build ID %s is not in BUILDING state' % build_id) - if buildinfo['name'] != metadata['build']['name'] or \ - buildinfo['version'] != metadata['build']['version'] or \ - buildinfo['release'] != metadata['build']['release']: + if buildinfo['draft'] != metadata['build'].get('draft', False): + raise koji.GenericError("Draft field does not match reservation (build id = %s)" + % build_id) + if (buildinfo['name'] != metadata['build']['name'] or + buildinfo['version'] != metadata['build']['version']): + raise koji.GenericError("Build (%i) NVR is different" % build_id) + # release is complicated by draft field + if buildinfo['draft']: + # metadata should have the target release, convert it before we check + release = koji.gen_draft_release(metadata['build']['release'], build_id) + else: + release = metadata['build']['release'] + if buildinfo['release'] != release: raise koji.GenericError("Build (%i) NVR is different" % build_id) if ('epoch' in metadata['build'] and buildinfo['epoch'] != metadata['build']['epoch']): @@ -7070,16 +7080,16 @@ class CG_Importer(object): elif token is not None: raise koji.GenericError('Reservation token given, but no build_id ' 'in metadata') + else: # no build reservation buildinfo = get_build(metadata['build'], strict=False) if buildinfo: if (koji.BUILD_STATES[buildinfo['state']] not in ('CANCELED', 'FAILED')): - raise koji.GenericError("Build already exists: %r" % buildinfo) + raise koji.GenericError("Build already exists: %(nvr)s (id=%(id)s)" + % buildinfo) # note: the checks in recycle_build will also apply when we call new_build later - - if buildinfo: - reject_draft(buildinfo) + # our state check is stricter than the one in recycle_build # gather needed data buildinfo = dslice(metadata['build'], ['name', 'version', 'release', 'extra', 'source']) @@ -7087,6 +7097,7 @@ class CG_Importer(object): buildinfo['build_id'] = metadata['build']['build_id'] # epoch is not in the metadata spec, but we allow it to be specified buildinfo['epoch'] = metadata['build'].get('epoch', None) + buildinfo['draft'] = metadata['build'].get('draft', False) buildinfo['start_time'] = convert_timestamp(float(metadata['build']['start_time'])) buildinfo['completion_time'] = convert_timestamp(float(metadata['build']['end_time'])) owner = metadata['build'].get('owner', None) diff --git a/tests/test_cli/data/list-commands-admin.txt b/tests/test_cli/data/list-commands-admin.txt index 3122eb63..66776194 100644 --- a/tests/test_cli/data/list-commands-admin.txt +++ b/tests/test_cli/data/list-commands-admin.txt @@ -55,6 +55,7 @@ admin commands: remove-tag Remove a tag remove-tag-inheritance Remove a tag inheritance link remove-target Remove a build target + reserve-cg Reserve a build entry for later import restart-hosts Restart enabled hosts revoke-cg-access Remove a user from a content generator revoke-permission Revoke a permission from a user diff --git a/tests/test_cli/data/list-commands.txt b/tests/test_cli/data/list-commands.txt index 38041dd3..a2057720 100644 --- a/tests/test_cli/data/list-commands.txt +++ b/tests/test_cli/data/list-commands.txt @@ -55,6 +55,7 @@ admin commands: remove-tag Remove a tag remove-tag-inheritance Remove a tag inheritance link remove-target Remove a build target + reserve-cg Reserve a build entry for later import restart-hosts Restart enabled hosts revoke-cg-access Remove a user from a content generator revoke-permission Revoke a permission from a user diff --git a/tests/test_cli/test_import_cg.py b/tests/test_cli/test_import_cg.py index d384dcb6..e1bfb3eb 100644 --- a/tests/test_cli/test_import_cg.py +++ b/tests/test_cli/test_import_cg.py @@ -185,11 +185,13 @@ class TestImportCG(utils.CliTestCase): (Specify the --help global option for a list of other help options) Options: - -h, --help show this help message and exit - --noprogress Do not display progress of the upload - --link Attempt to hardlink instead of uploading - --test Don't actually import - --token=TOKEN Build reservation token + -h, --help show this help message and exit + --noprogress Do not display progress of the upload + --link Attempt to hardlink instead of uploading + --test Don't actually import + --token=TOKEN Build reservation token + --draft Import as a draft + --build-id=BUILD_ID Reserved build id """ % self.progname)