#!/usr/bin/env python3 """ Refreshes the data/JIRA/*.json files For each .json file found under data/JSON (apart from jira_projects.json) it recreates the file. Also refreshes data/JIRA/jira_projects.json It does not use the contents of jira_projects.json to refresh the files, because not all of the entries are needed. However, once a file has been added to the directory, it will be kept up to date. It sleeps(2) between fetches. This script is run under cron, so does not check the age of any files. It is assumed that the job is run as frequently as necessary to keep the files updated """ import errtee # this is used, even though it is not referenced import os, json, time, sys import requests from os import listdir from os.path import isfile, join, dirname, abspath from inspect import getsourcefile sys.path.append(".") # module is in current directory import jira_auth # MYHOME = "/var/www/reporter.apache.org" # Assume we are one directory below RAO (scripts) MYHOME = dirname(dirname(abspath(getsourcefile(lambda:0)))) # automatically work out home location so can run the code anywhere JIRA_REST = 'https://issues.apache.org/jira/rest/' JIRA_AUTH = JIRA_REST + 'auth/1/session' # POST or DELETE JIRA_PROJ = JIRA_REST + 'api/2/project.json' JIRA_QURY = JIRA_REST + "api/2/search?jql=project='%s'+AND+%s%%3E=-91d&fields=key" PROJECT_JSON = f"{MYHOME}/data/JIRA/jira_projects.json" mypath = "%s/data/JIRA" % MYHOME print("Scanning mypath=%s" % mypath) myfiles = [ f for f in listdir(mypath) if f.endswith(".json") and isfile(join(mypath,f)) ] JIRA_SLEEP = 0.5 # how long to wait between API requests JIRATIMEOUT=90 # This may need to be adjusted jiraerrors = 0 # count how many consecutive errors occurred def clearErrors(): global jiraerrors jiraerrors = 0 def handleError(): global jiraerrors jiraerrors += 1 if jiraerrors > 5: sys.exit(f"Too many errors - {jiraerrors} - quitting. See /var/log/www-data/readjira.log for details.") hdrs = {"content-type": "application/json"}.update(jira_auth.auth_header()) def getJsonPath(project): return f"{MYHOME}/data/JIRA/{project}.json" def getProjects(): """Update the list of projects in data/JIRA/jira_projects.json""" print(f"Refresh {PROJECT_JSON}") try: res = requests.get(JIRA_PROJ, headers=hdrs, timeout=JIRATIMEOUT) res.raise_for_status() jzon = res.json() with open(PROJECT_JSON, "w") as f: json.dump(jzon, f, indent=1, sort_keys=True) print(f"Created {PROJECT_JSON}") # Remove retired entries for entry in jzon: cat = entry.get('projectCategory','') if cat and cat['name'] == 'Retired': project = entry['key'] file = getJsonPath(project) if isfile(file): print(f"Retired project: {project}, removing {file}") os.remove(file) except Exception as e: print(f"Err: could not refresh {PROJECT_JSON}: {e}") sys.exit("Failed to update project list - quitting. See /var/log/www-data/readjira.log for details.") def getJIRAS(project): file = getJsonPath(project) created = JIRA_QURY % (project, 'created') resolved = JIRA_QURY % (project, 'resolved') res = None try: time.sleep(JIRA_SLEEP) # don't overload Jira res = requests.get(created, headers=hdrs, timeout=JIRATIMEOUT) res.raise_for_status() cdata = res.json() time.sleep(JIRA_SLEEP) # don't overload Jira res = requests.get(resolved, headers=None, timeout=JIRATIMEOUT) res.raise_for_status() rdata = res.json() with open(file, "w") as f: json.dump([cdata['total'], rdata['total'], project], f, indent=1, sort_keys=True) clearErrors() return cdata['total'], rdata['total'], project except requests.exceptions.RequestException as err: # If a project cannot be found, the API returns code 400 with JSON text of the form: # {"errorMessages":["The value 'DUMMY' does not exist for the field 'project'."],"errors":{}} # For some errors (e.g. auth fail), the API returns 401 or 403, in which case the body is HTML rather than JSON ct = res.headers['content-type'] if ct.startswith('application/json'): response = res.json()['errorMessages'][0] elif 'www-authenticate' in res.headers: response = 'Authentication failure' else: response = str(res.headers) print("Failed to get data for %s: %s %s" % (project, err, response)) if "does not exist for the field 'project'" in response: print("Removing %s" % file) os.remove(file) # Don't write an empty file handleError() return 0,0, None getProjects() for project in myfiles: jiraname = project.replace(".json", "") if jiraname != "jira_projects": print("Refreshing JIRA stats for " + jiraname) getJIRAS(jiraname) print("Done")