Skip to content

Instantly share code, notes, and snippets.

@mricon
Last active July 27, 2016 08:38
Show Gist options
  • Save mricon/432cbdf8ee6fe20a78f02a2075e5333b to your computer and use it in GitHub Desktop.
Save mricon/432cbdf8ee6fe20a78f02a2075e5333b to your computer and use it in GitHub Desktop.
#!/usr/bin/python2 -tt
#
# bugzilla.kernel.org bug junker
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import sys
import bugzilla
import logging
import codecs
import re
UTF8Writer = codecs.getwriter('utf8')
sys.stdout = UTF8Writer(sys.stdout)
VERSION = '0.0.2'
# Your ~/.bugzillarc should have the necessary user/password entries
# for the script to connect with privileges of the account who can junk
# bugs and ban users. Alternatively, use a config file with user/password
# and other entries and pass it via -c (see example below).
#
BZ_URL = 'https://bugzilla.kernel.org'
#
# What should we change the spammy bugs to?
SET_STATUS = 'RESOLVED'
SET_RESOLUTION = 'INVALID'
#
# Make a private group unavailable to anyone but admins
# and set spammy bugs to be part of that group.
SET_GROUP = 'Junk'
#
# Example config file:
# [main]
# url = https://bugzilla.kernel.org
# user = [email protected]
# password = example
# status = RESOLVED
# resolution = INVALID
# group = Junk
# logfile = /path/to/bugjunker.log
# keywords = phone number
# support number
# customer service
# tech support number
# quickbooks
# norton antivirus
logger = logging.getLogger(__name__)
bz = None
DRYRUN = False
def junk_by_ids(bugids):
logger.info('Preparing to junk %s bugs' % len(bugids))
seen_creators = []
qbugs = bz.getbugs(bugids, include_fields=['id', 'creator'])
for qbug in qbugs:
if qbug.creator not in seen_creators:
seen_creators.append(qbug.creator)
ban_user(qbug.creator)
# get all bugs they created and junk those, too
for cbug in get_unjunked_by_creator(qbug.creator):
if cbug.id not in bugids:
bugids.append(cbug.id)
logger.info('Junking %s bugs' % len(bugids))
vals = bz.build_update(
status=SET_STATUS,
resolution=SET_RESOLUTION,
groups_add=[SET_GROUP,]
)
if not DRYRUN:
bz.update_bugs(bugids, vals)
def ban_user(username):
logger.info('Banning user: %s' % username)
ban_vals = {
'names': [username,],
'email_enabled': 0,
'login_denied_text': 'Spammer',
}
if not DRYRUN:
bz._proxy.User.update(ban_vals)
def get_unjunked_by_creator(creator):
query = bz.build_query(
reporter=creator,
include_fields=['id', 'summary', 'groups', 'resolution']
)
bugs = get_unjunked_by_query(query)
logger.info('Found %s bugs opened by %s' % (len(bugs), creator))
return bugs
def get_unjunked_by_query(query):
bugs = []
qres = bz.query(query)
for abug in qres:
if abug.resolution != SET_RESOLUTION or SET_GROUP not in abug.groups:
bugs.append(abug)
return bugs
def main():
from optparse import OptionParser
usage = '''usage: quickly junk bugzilla spam
This program will find spammy bugs, hide them from view, ban the
users who created these bugs, and also find all other bugs these users
created and hide them as well.
Best usage is to generate a config file and run it from cron every
5-10 minutes or so, and then use a local copy of the script to hit
one-off bugs that got missed (there will always be some).
E.g., from cron:
%prog -q -c /etc/bugjunker.conf
Locally (recommend using -i, to review the list first):
%prog -i -k 'phone support,antivirus'
One-off bugs best hit with a straight-out -b:
%prog -b 555121,555122
'''
op = OptionParser(usage=usage, version=VERSION)
op.add_option('-c', '--config-file', dest='configfile',
help='Get options from a config file (useful for cron runs)')
op.add_option('-u', '--bugzilla-url', dest='bzurl',
default=BZ_URL,
help='Bugzilla URL (default: %default)')
op.add_option('-k', '--ban-by-keyword', dest='keywords',
help='Ban bugs by keyword (separate multiple with comma)')
op.add_option('-i', '--interactive', dest='interactive',
action='store_true', default=False,
help='Ask before junking bugs (use with -k)')
op.add_option('-b', '--bugs', dest='buglist',
help='Ban bugs by number (separate multiple with comma)')
op.add_option('-j', '--junkfile', dest='junkfile',
help='Load bug IDs from a file (one ID per line)')
op.add_option('-q', '--quiet', dest='quiet', action='store_true',
default=False,
help='Suppress output not otherwise going to a logfile')
op.add_option('-d', '--dry-run', dest='dryrun', action='store_true',
default=False,
help='Perform a dry-run and make no actual changes')
opts, args = op.parse_args()
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
ch = logging.StreamHandler()
ch.setFormatter(logging.Formatter('%(message)s'))
if opts.quiet and not opts.interactive:
ch.setLevel(logging.CRITICAL)
elif opts.dryrun:
global DRYRUN
DRYRUN = True
ch.setFormatter(logging.Formatter('DRYRUN: %(message)s'))
ch.setLevel(logging.DEBUG)
else:
ch.setLevel(logging.INFO)
logger.addHandler(ch)
logger.info('Starting a bugjunker run')
if opts.bzurl and opts.configfile and opts.bzurl != BZ_URL:
op.error('Cannot use -u with -c (url comes from config file)')
if opts.bzurl:
bzurl = opts.bzurl
else:
bzurl = BZ_URL
keywords = []
global bz
if opts.configfile:
logger.info('Loading configuration file %s' % opts.configfile)
from ConfigParser import ConfigParser
config = ConfigParser()
config.read(opts.configfile)
if config.get('main', 'logfile', None):
ch = logging.FileHandler(config.get('main', 'logfile'))
fmt = '[%(process)d] %(asctime)s - %(message)s'
if opts.dryrun:
fmt = '[DRYRUN] %s' % fmt
ch.setFormatter(logging.Formatter(fmt))
ch.setLevel(logging.INFO)
logger.addHandler(ch)
user = config.get('main', 'user', None)
password = config.get('main', 'password', None)
bzurl = config.get('main', 'url', None)
if not bzurl:
bzurl = BZ_URL
global SET_STATUS, SET_RESOLUTION, SET_GROUP
set_status = config.get('main', 'status', None)
if set_status:
SET_STATUS = set_status
set_resolution = config.get('main', 'resolution', None)
if set_resolution:
SET_RESOLUTION = set_resolution
set_group = config.get('main', 'group', None)
if set_group:
SET_GROUP = set_group
kwlist = config.get('main', 'keywords')
if len(kwlist):
keywords = kwlist.split('\n')
logger.info('Logging in to %s' % bzurl)
if user is not None and password is not None:
logger.info('Using credentials from config file (user=%s)' % user)
bz = bugzilla.Bugzilla(url=bzurl, user=user, password=password)
else:
# assuming this will come from ~/.bugzillarc or /etc/bugzillarc
logger.info('Using credentials from bugzillarc')
bz = bugzilla.Bugzilla(url=bzurl)
else:
logger.info('Using %s with credentials from bugzillarc' % bzurl)
bz = bugzilla.Bugzilla(url=bzurl)
if keywords and opts.keywords:
op.error('Cannot use -k with -c (keywords come from config file)')
if opts.keywords:
keywords = opts.keywords.split(',')
buglist = []
if keywords:
logger.info('Using keywords: %s' % keywords)
for keyword in keywords:
# get all NEW bugs matching a keyword
query = {
'summary': keyword,
'status': 'NEW',
'include_fields': ['id', 'summary', 'groups', 'resolution'],
}
abugs = get_unjunked_by_query(query)
if abugs:
logger.info('Found %s bugs matching keyword "%s"'
% (len(abugs), keyword))
for abug in abugs:
buglist.append(abug.id)
logger.info('%s - %s' % (abug.id, abug.summary))
if not buglist:
logger.info('Zarro boogs found matching these keywords')
sys.exit(0)
if opts.interactive:
sys.stdout.write('Really junk the above bugs? [Y/n] ')
really = raw_input()
if really != 'y':
sys.exit(255)
elif opts.junkfile:
logger.info('Loading bug IDs from %s' % opts.junkfile)
fh = open(opts.junkfile)
bugre = re.compile('^(\d+)')
while True:
line = fh.readline()
if not line:
break
if line[0] == '#':
continue
reres = bugre.match(line)
if reres:
buglist.append(int(reres.group(0)))
if not len(buglist):
logger.info('Zarro boogs found in %s' % opts.junkfile)
sys.exit(1)
fh.close()
elif opts.buglist:
buglist = [int(bugid) for bugid in opts.buglist.split(',')]
junk_by_ids(buglist)
if __name__ == '__main__':
main()
@unixbhaskar
Copy link

Wonderful Konstantin! need to give it a shot.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment