import sys if sys.hexversion < 0x030000F0: raise RuntimeError("This script requires Python3") """ This script runs a couple of Bugzilla searches: - all bugs that were created in the last 3 months - all RESOLVED bugs that changed to RESOLVED in the last 3 months and creates an output file: { "pmc1" : [created, resolved], "pmc2: ... } Output: ../data/bugzillastats.json """ import errtee # this is used, even though it is not referenced import json import requests from collections import defaultdict # There are 3 Bugzilla instances BASE = "https://bz.apache.org/bugzilla/rest.cgi/bug?include_fields=product" BASEOO = "https://bz.apache.org/ooo/rest.cgi/bug?include_fields=product" BASESA = "https://bz.apache.org/SpamAssassin/rest.cgi/bug?include_fields=product" CREATED = "&f1=creation_ts&o1=greaterthaneq&v1=-3m" RESOLVED = "&chfield=bug_status&chfieldvalue=RESOLVED&chfieldfrom=-3m&chfieldto=Now" # Maintained in Puppet; contain api key(s) BUGZILLA_CREDFILE = '/usr/local/etc/tokens/bugzilla%s.txt' __BUGZILLA_STATS = "../data/bugzillastats.json" def getapikey(suffix=''): file = BUGZILLA_CREDFILE % suffix try: with open(file, "r") as f: apikey = f.read().strip() except Exception: raise RuntimeError(f"Failed to read/parse Bugzilla credentials {file}") if apikey == '*': # for local testing import getpass apikey = getpass.getpass(f"API key #{suffix}: ") return apikey def getCSV(url, apikey): csv = defaultdict(int) rsp = requests.get(f"{url}&api_key={apikey}") # We don't use raise_for_status() because that would reveal the API key if not rsp.status_code == 200: raise RuntimeError(f"Code {rsp.status_code} getting {url}") text = rsp.text bugs = json.loads(text)['bugs'] for k in bugs: csv[k['product']] += 1 return csv # test data: # created = {'JMeter': 57, 'Ant': 15, 'APR': 10, 'Log4j': 3, 'WebSH': 1, 'Tomcat 9': 4, 'Apache httpd-2': 64, # 'Tomcat 7': 25, 'Tomcat Connectors': 8, 'Tomcat 6': 4, 'Taglibs': 1, 'Tomcat Native': 6, 'Tomcat 8': 85, 'POI': 90} # resolved = {'JMeter': 33, 'Ant': 8, 'APR': 2, 'Log4j': 2, 'WebSH': 1, 'Tomcat 9': 3, 'Apache httpd-2': 35, # 'Tomcat 7': 26, 'Tomcat Modules': 1, 'Tomcat Connectors': 7, 'Tomcat 6': 5, 'Taglibs': 2, 'Tomcat Native': 1, 'Tomcat 8': 88, 'POI': 115, 'Rivet': 0} print("Getting Bugzilla API keys") apikey = getapikey() apikeyoo = getapikey('oo') apikeysa = getapikey('sa') stats = {} # Other bugzilla users are Ant, APR, JMeter, POI prod2pmc = { 'log4j': 'logging', 'websh': 'tcl', 'rivet': 'tcl', 'taglibs': 'tomcat', } def getPMC(product): if product.startswith("Tomcat "): return 'tomcat' if product.startswith('Apache httpd'): return 'httpd' low = product.lower() if low in prod2pmc: return prod2pmc[low] return low def addCount(product, index, count, pmcoverride=None): pmc = pmcoverride or getPMC(product) if not pmc in stats: stats[pmc]=[0,0,{}] stats[pmc][index] += count try: stats[pmc][2][product][index] += count except KeyError: stats[pmc][2][product] = [0,0] stats[pmc][2][product][index] = count def getCounts(base, apikey, override=None): print(f"Getting list of {override or ''} bugs created in the last 3 months") created = getCSV(base + CREATED, apikey) for product in created: addCount(product, 0, created[product], override) print(f"Getting list of {override or ''} bugs resolved in the last 3 months") resolved = getCSV(base + RESOLVED, apikey) for product in resolved: addCount(product, 1, resolved[product], override) getCounts(BASE, apikey) getCounts(BASEOO, apikeyoo, 'openoffice') getCounts(BASESA, apikeysa, 'spamassassin') print("Writing " + __BUGZILLA_STATS) with open(__BUGZILLA_STATS,"w") as f: json.dump(stats, f, indent=1, sort_keys=True) f.close() print("All done")