Author: sebb
Date: Sat Aug  9 12:28:16 2025
New Revision: 1927713

Log:
Use session cookie to avoid repeated authn
Simplify code by using requests

Modified:
   comdev/reporter.apache.org/trunk/scripts/readjira.py

Modified: comdev/reporter.apache.org/trunk/scripts/readjira.py
==============================================================================
--- comdev/reporter.apache.org/trunk/scripts/readjira.py        Sat Aug  9 
12:10:02 2025        (r1927712)
+++ comdev/reporter.apache.org/trunk/scripts/readjira.py        Sat Aug  9 
12:28:16 2025        (r1927713)
@@ -15,9 +15,8 @@
 """
 
 import errtee # this is used, even though it is not referenced
-import os, json, base64, time, sys
-from urllib.request import urlopen, Request
-from urllib.error import HTTPError
+import os, json, time, sys
+import requests
 from os import listdir
 from os.path import isfile, join, dirname, abspath
 from inspect import getsourcefile
@@ -25,6 +24,14 @@ from inspect import getsourcefile
 # 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)) ]
@@ -34,9 +41,7 @@ with open("/usr/local/etc/tokens/jira.tx
     jirapass = f.read().strip()
     f.close()
 
-__AUTHSTRING = '%s:%s' % ('githubbot', jirapass)
-base64string = 
base64.b64encode(__AUTHSTRING.encode(encoding='utf-8')).decode(encoding='utf-8')
-
+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():
@@ -49,44 +54,65 @@ def handleError():
     if jiraerrors > 5:
         sys.exit(f"Too many errors - {jiraerrors} - quitting. See 
/var/log/www-data/readjira.log for details.")
 
+def getCookie():
+    hdrs = {"content-type": "application/json"}
+    data = {"username": "githubbot", "password": jirapass } # needs to be JSON
+    res = requests.post(JIRA_AUTH, headers=hdrs, json=data, 
timeout=JIRATIMEOUT)
+    res.raise_for_status()
+    session = res.json()['session']
+    if session['name'] == 'JSESSIONID':
+        return session['value']
+    else:
+        sys.exit("Failed to establish session cookie - quitting.")
+
+hdrs = {"cookie": f"JSESSIONID={getCookie()}; Path=/; HttpOnly;"}
+
+def logout():
+    res = requests.delete(JIRA_AUTH, headers=hdrs, timeout=JIRATIMEOUT)
+    res.raise_for_status()
+
 def getProjects():
     """Update the list of projects in data/JIRA/jira_projects.json"""
-    PROJECT_JSON = "%s/data/JIRA/jira_projects.json" % MYHOME
-    x = {}
-    print("Refresh %s" % PROJECT_JSON)
+    print(f"Refresh {PROJECT_JSON}")
     try:
-        req = Request("https://issues.apache.org/jira/rest/api/2/project.json";)
-        req.add_header("Authorization", "Basic %s" % base64string)
-        x = json.loads(urlopen(req, 
timeout=JIRATIMEOUT).read().decode('utf-8'))
+        res = requests.get(JIRA_PROJ, headers=hdrs, timeout=JIRATIMEOUT)
+        res.raise_for_status()
         with open(PROJECT_JSON, "w") as f:
-            json.dump(x, f, indent=1, sort_keys=True)
-            f.close()
-        print("Created %s" % PROJECT_JSON)
-        clearErrors()
+            json.dump(res.json(), f, indent=1, sort_keys=True)
+        print(f"Created {PROJECT_JSON}")
     except Exception as e:
-        print("Err: could not refresh %s: %s" % (PROJECT_JSON, e))
-        handleError()
+        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 = "%s/data/JIRA/%s.json" % (MYHOME, project)
+    file = f"{MYHOME}/data/JIRA/{project}.json"
+    created =  JIRA_QURY % (project, 'created')
+    resolved = JIRA_QURY % (project, 'resolved')
+    res = None
     try:
-        req = 
Request("""https://issues.apache.org/jira/rest/api/2/search?jql=project='""" + 
project + """'+AND+created%3E=-91d&fields=key""") # >=
-        req.add_header("Authorization", "Basic %s" % base64string)
-        cdata = json.loads(urlopen(req, 
timeout=JIRATIMEOUT).read().decode('utf-8'))
-        req = 
Request("""https://issues.apache.org/jira/rest/api/2/search?jql=project='""" + 
project + """'+AND+resolved%3E=-91d&fields=key""") # >=
-        req.add_header("Authorization", "Basic %s" % base64string)
-        rdata = json.loads(urlopen(req, 
timeout=JIRATIMEOUT).read().decode('utf-8'))
+        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 Exception as err:
-        response = ''
+    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
-        if isinstance(err, HTTPError):
-            response = err.read().decode('utf-8') # needed to check for 
missing project
+        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)
@@ -102,6 +128,7 @@ for project in myfiles:
     if jiraname != "jira_projects":
         print("Refreshing JIRA stats for " + jiraname)
         getJIRAS(jiraname)
-        time.sleep(2)
+
+logout()
 
 print("Done")
\ No newline at end of file

Reply via email to