diff --git a/builder/kojid b/builder/kojid index 73285798..72f66a1e 100755 --- a/builder/kojid +++ b/builder/kojid @@ -986,6 +986,12 @@ class TaskManager(object): for task in tasks: # note: tasks are in priority order self.logger.debug("task: %r" % task) + if self.tasks.has_key(task['id']): + # we were running this task, but it apparently has been + # freed or reassigned. We can't do anything with it until + # updateTasks notices this and cleans up. + self.logger.debug("Task %(id)s freed or reassigned", task) + continue if task['state'] == koji.TASK_STATES['ASSIGNED']: self.logger.debug("task is assigned") if self.host_id == task['host_id']: diff --git a/cli/koji b/cli/koji index 0db0b24b..50af986d 100755 --- a/cli/koji +++ b/cli/koji @@ -2050,6 +2050,8 @@ def anon_handle_list_tagged(options, session, args): parser.add_option("--paths", action="store_true", help=_("Show the file paths")) parser.add_option("--sigs", action="store_true", help=_("Show signatures")) parser.add_option("--maven", action="store_true", help=_("Show Maven builds only")) + parser.add_option("--event", type='int', metavar="EVENT#", help=_("query at event")) + parser.add_option("--repo", type='int', metavar="REPO#", help=_("query at event for a repo")) (options, args) = parser.parse_args(args) if len(args) == 0: parser.error(_("A tag name must be specified")) @@ -2076,6 +2078,16 @@ def anon_handle_list_tagged(options, session, args): options.rpms = True if options.maven and not options.rpms: opts['maven_only'] = True + if options.event is not None: + opts['event'] = options.event + if options.repo is not None: + repo = session.repoInfo(options.repo) + if not repo: + print "No such repo: %s" % options.repo + return + repo['timestr'] = time.asctime(time.localtime(repo['create_ts'])) + print "Querying at event %(create_event)i (%(tag_name)s/%(timestr)s)" % repo + opts['event'] = repo['create_event'] if options.rpms: rpms, builds = session.listTaggedRPMS(tag, **opts) @@ -2491,6 +2503,14 @@ def anon_handle_rpminfo(options, session, args): print "Payload: %(payloadhash)s" %info print "Size: %(size)s" %info print "Build ID: %(build_id)s" %info + if info['buildroot_id'] is None: + print "No buildroot data available" + else: + br_info = session.getBuildroot(info['buildroot_id']) + print "Buildroot: %(id)i (tag %(tag_name)s, arch %(arch)s, repo %(repo_id)i)" % br_info + print "Build Host: %(host_name)s" % br_info + print "Build Task: %(task_id)i" % br_info + def anon_handle_buildinfo(options, session, args): "Print basic information about a build" @@ -3882,35 +3902,57 @@ def handle_set_pkg_owner_global(options, session, args): parser = OptionParser(usage=usage) parser.add_option("--verbose", action='store_true', help=_("List changes")) parser.add_option("--test", action='store_true', help=_("Test mode")) + parser.add_option("--old-user", "--from", action="store", help=_("Only change ownership for packages belonging to this user")) (options, args) = parser.parse_args(args) - if len(args) < 2: + if options.old_user: + if len(args) < 1: + parser.error(_("Please specify an owner")) + assert False + elif len(args) < 2: parser.error(_("Please specify an owner and at least one package")) assert False activate_session(session) owner = args[0] + packages = args[1:] user = session.getUser(owner) if not user: print "No such user: %s" % owner sys.exit(1) - for package in args[1:]: - entries = session.listPackages(pkgID=package, with_dups=True) + opts = {'with_dups' : True} + old_user = None + if options.old_user: + old_user = session.getUser(options.old_user) + if not old_user: + print _("No such user: %s") % options.old_user + sys.exit(1) + opts['userID'] = old_user['id'] + to_change = [] + for package in packages: + entries = session.listPackages(pkgID=package, **opts) if not entries: print "No data for package %s" % package continue - for entry in entries: - if user['id'] == entry['owner_id']: - if options.verbose: - print "Preserving owner=%s for package %s in tag %s" \ - % (user['name'], package, entry['tag_name'] ) - else: - if options.test: - print "Would have changed owner for %s in tag %s: %s -> %s" \ - % (package, entry['tag_name'], entry['owner_name'], user['name']) - continue - if options.verbose: - print "Changing owner for %s in tag %s: %s -> %s" \ - % (package, entry['tag_name'], entry['owner_name'], user['name']) - session.packageListSetOwner(entry['tag_id'], package, user['id']) + to_change.extend(entries) + if not packages and options.old_user: + entries = session.listPackages(**opts) + if not entries: + print "No data for user %s" % old_user['name'] + sys.exit(1) + to_change.extend(entries) + for entry in to_change: + if user['id'] == entry['owner_id']: + if options.verbose: + print "Preserving owner=%s for package %s in tag %s" \ + % (user['name'], package, entry['tag_name'] ) + else: + if options.test: + print "Would have changed owner for %s in tag %s: %s -> %s" \ + % (entry['package_name'], entry['tag_name'], entry['owner_name'], user['name']) + continue + if options.verbose: + print "Changing owner for %s in tag %s: %s -> %s" \ + % (entry['package_name'], entry['tag_name'], entry['owner_name'], user['name']) + session.packageListSetOwner(entry['tag_id'], entry['package_name'], user['id']) def anon_handle_watch_task(options, session, args): "Track progress of particular tasks" @@ -4042,7 +4084,7 @@ def handle_move_pkg(options, session, args): if _running_in_bg() or options.nowait: return else: - watch_tasks(session,tasks) + return watch_tasks(session, tasks) def handle_untag_pkg(options, session, args): "Remove a tag from one or more packages" @@ -4050,22 +4092,69 @@ def handle_untag_pkg(options, session, args): usage += _("\n(Specify the --help global option for a list of other help options)") parser = OptionParser(usage=usage) parser.add_option("--all", action="store_true", help=_("untag all versions of the package in this tag")) + parser.add_option("--non-latest", action="store_true", help=_("untag all versions of the package in this tag except the latest")) + parser.add_option("-n", "--test", action="store_true", help=_("test mode")) + parser.add_option("-v", "--verbose", action="store_true", help=_("print details")) parser.add_option("--force", action="store_true", help=_("force operation")) (options, args) = parser.parse_args(args) - if len(args) < 2: + if options.non_latest and options.force: + if len(args) < 1: + parser.error(_("Please specify a tag")) + assert False + elif len(args) < 2: parser.error(_("This command takes at least two arguments: a tag name/ID and one or more package n-v-r's")) assert False activate_session(session) + tag = session.getTag(args[0]) + if not tag: + parser.error(_("Invalid tag: %s" % args[0])) if options.all: - pkgs = [] + builds = [] for pkg in args[1:]: - pkgs.extend([x['nvr'] for x in session.listTagged(args[0], package=pkg)]) + builds.extend(session.listTagged(args[0], package=pkg)) + elif options.non_latest: + if options.force and len(args) == 1: + tagged = session.listTagged(args[0]) + else: + tagged = [] + for pkg in args[1:]: + tagged.extend(session.listTagged(args[0], package=pkg)) + # listTagged orders entries latest first + seen_pkg = {} + builds = [] + for binfo in tagged: + if not seen_pkg.has_key(binfo['name']): + #latest for this package + if options.verbose: + print _("Leaving latest build for package %(name)s: %(nvr)s") % binfo + else: + builds.append(binfo) + seen_pkg[binfo['name']] = 1 else: - pkgs = args[1:] - for pkg in pkgs: - print pkg - #XXX trap errors - session.untagBuild(args[0], pkg, force=options.force) + tagged = session.listTagged(args[0]) + idx = dict([(b['nvr'], b) for b in tagged]) + builds = [] + for nvr in args[1:]: + binfo = idx.get(nvr) + if binfo: + builds.append(binfo) + else: + # not in tag, see if it even exists + binfo = session.getBuild(nvr) + if not binfo: + print _("No such build: %s") % nvr + else: + print _("Build %s not in tag %s") % (nvr, tag['name']) + if not options.force: + sys.exit(1) + builds.reverse() + for binfo in builds: + if options.test: + print _("would have untagged %(nvr)s") % binfo + else: + if options.verbose: + print _("untagging %(nvr)s") % binfo + session.untagBuild(tag['name'], binfo['nvr'], force=options.force) def handle_unblock_pkg(options, session, args): "[admin] Unblock a package in the listing for tag" diff --git a/hub/kojihub.py b/hub/kojihub.py index 267dd90d..014eeabe 100644 --- a/hub/kojihub.py +++ b/hub/kojihub.py @@ -480,18 +480,6 @@ def eventCondition(event, table=None): else: raise koji.GenericError, "Invalid event: %r" % event -def get_last_event(): - """ - Get the id and timestamp of the last event that modified the - system. Returns a map containing the following fields: - - id - - ts - """ - fields = ('id', 'ts') - q = """SELECT id, EXTRACT(EPOCH FROM time) FROM events - ORDER BY id DESC LIMIT 1""" - return _singleRow(q, {}, fields, strict=True) - def readGlobalInheritance(event=None): c=context.cnx.cursor() fields = ('tag_id','parent_id','name','priority','maxdepth','intransitive', @@ -1061,13 +1049,10 @@ def readTaggedRPMS(tag, package=None, arch=None, event=None,inherit=False,latest if isinstance(arch, basestring): q += """AND rpminfo.arch = %(arch)s """ + elif isinstance(arch, (list, tuple)): + q += """AND rpminfo.arch IN %(arch)s\n""" else: - try: - it = iter(arch) - except TypeError: - raise koji.GenericError, 'invalid arch option: %s' % arch - q += """AND rpminfo.arch in (%s) - """ % ','.join(["'%s'" % a for a in it]) + raise koji.GenericError, 'invalid arch option: %s' % arch # unique constraints ensure that each of these queries will not report # duplicate rpminfo entries, BUT since we make the query multiple times, @@ -2601,8 +2586,10 @@ def get_build(buildInfo, strict=False): owner_id: ID of the user who kicked off the build owner_name: name of the user who kicked off the build creation_event_id: id of the create_event - creation_time: time the build was created + creation_time: time the build was created (text) + creation_ts: time the build was created (epoch) completion_time: time the build was completed (may be null) + completion_ts: time the build was completed (epoch, may be null) If there is no build matching the buildInfo given, and strict is specified, raise an error. Otherwise return None. @@ -2619,6 +2606,8 @@ def get_build(buildInfo, strict=False): ('build.task_id', 'task_id'), ('events.id', 'creation_event_id'), ('events.time', 'creation_time'), ('package.id', 'package_id'), ('package.name', 'package_name'), ('package.name', 'name'), ("package.name || '-' || build.version || '-' || build.release", 'nvr'), + ('EXTRACT(EPOCH FROM events.time)','creation_ts'), + ('EXTRACT(EPOCH FROM build.completion_time)','completion_ts'), ('users.id', 'owner_id'), ('users.name', 'owner_name')) query = """SELECT %s FROM build @@ -5197,7 +5186,28 @@ class RootExports(object): context.session.assertPerm('admin') return "%r" % context.opts - getLastEvent = staticmethod(get_last_event) + def getLastEvent(self, before=None): + """ + Get the id and timestamp of the last event recorded in the system. + Events are usually created as the result of a configuration change + in the database. + + If "before" (int or float) is specified, return the last event + that occurred before that time (in seconds since the epoch). + If there is no event before the given time, an error will be raised. + """ + fields = ('id', 'ts') + values = {} + q = """SELECT id, EXTRACT(EPOCH FROM time) FROM events""" + if before is not None: + if not isinstance(before, (int, long, float)): + raise koji.GenericError, 'invalid type for before: %s' % type(before) + # use the repr() conversion because it retains more precision than the + # string conversion + q += """ WHERE EXTRACT(EPOCH FROM time) < %(before)r""" + values['before'] = before + q += """ ORDER BY id DESC LIMIT 1""" + return _singleRow(q, values, fields, strict=True) def makeTask(self,*args,**opts): #this is mainly for debugging @@ -6093,7 +6103,10 @@ class RootExports(object): """Grant a permission to a user""" context.session.assertPerm('admin') user_id = get_user(userinfo,strict=True)['id'] - perm_id = get_perm_id(permission,strict=True) + perm = lookup_perm(permission, strict=True) + perm_id = perm['id'] + if perm['name'] in koji.auth.get_user_perms(user_id): + raise koji.GenericError, 'user %s already has permission: %s' % (userinfo, perm['name']) insert = """INSERT INTO user_perms (user_id, perm_id) VALUES (%(user_id)i, %(perm_id)i)""" _dml(insert, locals()) diff --git a/util/kojira b/util/kojira index c6803900..19d08e7f 100755 --- a/util/kojira +++ b/util/kojira @@ -119,8 +119,21 @@ class ManagedRepo(object): def tryDelete(self): """Remove the repo from disk, if possible""" - #we check just the event age first since it is faster - age = time.time() - self.event_ts + tag_info = session.getTag(self.tag_id) + if not tag_info: + self.logger.warn('Could not get info for tag %i, skipping delete of repo %i' % + (self.tag_id, self.repo_id)) + return False + tag_name = tag_info['name'] + path = pathinfo.repo(self.repo_id, tag_name) + try: + #also check dir age. We do this because a repo can be created from an older event + #and should not be removed based solely on that event's timestamp. + mtime = os.stat(path).st_mtime + except OSError: + self.logger.error("Can't stat repo directory: %s" % path) + return True + age = time.time() - max(self.event_ts, mtime) if age < options.deleted_repo_lifetime: #XXX should really be called expired_repo_lifetime return False @@ -133,23 +146,6 @@ class ManagedRepo(object): return False self.logger.info("Deleted repo %s" % self.repo_id) self.state = koji.REPO_DELETED - tag_info = session.getTag(self.tag_id) - if not tag_info: - self.logger.warn('Could not get info for tag %i, skipping delete of repo %i' % - (self.tag_id, self.repo_id)) - return False - tag_name = tag_info['name'] - path = pathinfo.repo(self.repo_id, tag_name) - #also check dir age. We do this because a repo can be created from an older event - #and should not be removed based solely on that event's timestamp. - try: - age = time.time() - os.stat(path).st_mtime - except OSError: - self.logger.error("Can't stat repo directory: %s" % path) - return True - if age < options.deleted_repo_lifetime: - #XXX should really be called expired_repo_lifetime - return False safe_rmtree(path, strict=False) return True