55import re
66import subprocess
77import sys
8+
89from jira import JIRA
9- from release_config import release_map , jira_field_map
1010
11- CVE_PATTERN = r'CVE-\d{4}-\d{4,7}'
11+ from release_config import jira_field_map , release_map
12+
13+ CVE_PATTERN = r"CVE-\d{4}-\d{4,7}"
1214
1315# Reverse lookup: field name -> custom field ID
1416jira_field_reverse = {v : k for k , v in jira_field_map .items ()}
1517
18+
1619def restore_git_branch (original_branch , kernel_src_tree ):
1720 """Restore the original git branch in case of errors."""
1821 try :
1922 subprocess .run (
20- ["git" , "checkout" , original_branch ],
21- cwd = kernel_src_tree ,
22- check = True ,
23- capture_output = True ,
24- text = True
23+ ["git" , "checkout" , original_branch ], cwd = kernel_src_tree , check = True , capture_output = True , text = True
2524 )
2625 except subprocess .CalledProcessError as e :
2726 print (f"ERROR: Failed to restore original branch { original_branch } : { e .stderr } " )
@@ -31,7 +30,7 @@ def restore_git_branch(original_branch, kernel_src_tree):
3130def main ():
3231 parser = argparse .ArgumentParser (
3332 description = "Validate PR Commits against JIRA VULN Tickets" ,
34- formatter_class = argparse .ArgumentDefaultsHelpFormatter
33+ formatter_class = argparse .ArgumentDefaultsHelpFormatter ,
3534 )
3635 # This is a necessary requirement at the moment to allow multiple different JIRA creds from the same user
3736 parser .add_argument (
@@ -73,9 +72,9 @@ def main():
7372 print (f"ERROR: Kernel source tree path does not exist: { args .kernel_src_tree } " )
7473 sys .exit (1 )
7574
76- jira_url = args .jira_url or os .environ .get (' JIRA_URL' )
77- jira_user = args .jira_user or os .environ .get (' JIRA_API_USER' )
78- jira_key = args .jira_key or os .environ .get (' JIRA_API_TOKEN' )
75+ jira_url = args .jira_url or os .environ .get (" JIRA_URL" )
76+ jira_user = args .jira_user or os .environ .get (" JIRA_API_USER" )
77+ jira_key = args .jira_key or os .environ .get (" JIRA_API_TOKEN" )
7978
8079 if not all ([jira_url , jira_user , jira_key ]):
8180 print ("ERROR: JIRA credentials not provided. Set via --jira-* args or environment variables." )
@@ -92,9 +91,7 @@ def main():
9291 try :
9392 # Get current branch to restore later
9493 result = subprocess .run (
95- ["git" , "branch" , "--show-current" ],
96- cwd = args .kernel_src_tree ,
97- check = True , capture_output = True , text = True
94+ ["git" , "branch" , "--show-current" ], cwd = args .kernel_src_tree , check = True , capture_output = True , text = True
9895 )
9996 original_branch = result .stdout .strip ()
10097 except subprocess .CalledProcessError as e :
@@ -104,11 +101,7 @@ def main():
104101 # Checkout the merge target branch first to ensure it exists
105102 try :
106103 subprocess .run (
107- ["git" , "checkout" , args .merge_target ],
108- cwd = args .kernel_src_tree ,
109- check = True ,
110- capture_output = True ,
111- text = True
104+ ["git" , "checkout" , args .merge_target ], cwd = args .kernel_src_tree , check = True , capture_output = True , text = True
112105 )
113106 except subprocess .CalledProcessError as e :
114107 print (f"ERROR: Failed to checkout merge target branch { args .merge_target } : { e .stderr } " )
@@ -119,11 +112,7 @@ def main():
119112 # Checkout the PR branch
120113 try :
121114 subprocess .run (
122- ["git" , "checkout" , args .pr_branch ],
123- cwd = args .kernel_src_tree ,
124- check = True ,
125- capture_output = True ,
126- text = True
115+ ["git" , "checkout" , args .pr_branch ], cwd = args .kernel_src_tree , check = True , capture_output = True , text = True
127116 )
128117 except subprocess .CalledProcessError as e :
129118 print (f"ERROR: Failed to checkout PR branch { args .pr_branch } : { e .stderr } " )
@@ -137,13 +126,13 @@ def main():
137126 cwd = args .kernel_src_tree ,
138127 check = True ,
139128 capture_output = True ,
140- text = True
129+ text = True ,
141130 )
142131 except subprocess .CalledProcessError as e :
143132 print (f"ERROR: failed to get commits: { e .stderr } " )
144133 sys .exit (1 )
145134
146- commit_shas = result .stdout .strip ().split (' \n ' ) if result .stdout .strip () else []
135+ commit_shas = result .stdout .strip ().split (" \n " ) if result .stdout .strip () else []
147136
148137 # Parse each commit and extract header
149138 commits_data = []
@@ -158,14 +147,14 @@ def main():
158147 cwd = args .kernel_src_tree ,
159148 check = True ,
160149 capture_output = True ,
161- text = True
150+ text = True ,
162151 )
163152 except subprocess .CalledProcessError as e :
164153 print (f"ERROR: Failed to get commits: { e .stderr } " )
165154 sys .exit (1 )
166155
167156 commit_msg = result .stdout .strip ()
168- lines = commit_msg .split (' \n ' )
157+ lines = commit_msg .split (" \n " )
169158
170159 # Extract summary line (first line)
171160 summary = lines [0 ] if lines else ""
@@ -189,23 +178,23 @@ def main():
189178 stripped = line .strip ()
190179
191180 # Check for jira line with VULN
192- if stripped .lower ().startswith (' jira ' ) and ' vuln-' in stripped .lower ():
181+ if stripped .lower ().startswith (" jira " ) and " vuln-" in stripped .lower ():
193182 parts = stripped .split ()
194183 for part in parts [1 :]: # Skip 'jira' keyword
195- if part .upper ().startswith (' VULN-' ):
184+ if part .upper ().startswith (" VULN-" ):
196185 vuln_tickets .append (part .upper ())
197186
198187 # Check for CVE line
199188 # Assume format: "cve CVE-YYYY-NNNN", "cve-bf CVE-YYYY-NNNN", or "cve-pre CVE-YYYY-NNNN"
200189 # There will only be one CVE per line, but possibly multiple CVEs listed
201- if stripped .lower ().startswith ((' cve ' , ' cve-bf ' , ' cve-pre ' )):
190+ if stripped .lower ().startswith ((" cve " , " cve-bf " , " cve-pre " )):
202191 parts = stripped .split ()
203192 for part in parts [1 :]: # Skip 'cve'/'cve-bf'/'cve-pre' keyword/tag
204193 # CVES always start with CVE-
205- if part .upper ().startswith (' CVE-' ):
194+ if part .upper ().startswith (" CVE-" ):
206195 commit_cves .append (part .upper ())
207196
208- header = ' \n ' .join (header_lines )
197+ header = " \n " .join (header_lines )
209198
210199 # Check VULN tickets against merge target
211200 lts_match = None
@@ -218,7 +207,7 @@ def main():
218207
219208 # Get LTS product
220209 lts_product_field = issue .get_field (jira_field_reverse ["LTS Product" ])
221- if hasattr (lts_product_field , ' value' ):
210+ if hasattr (lts_product_field , " value" ):
222211 lts_product = lts_product_field .value
223212 else :
224213 lts_product = str (lts_product_field ) if lts_product_field else None
@@ -248,48 +237,60 @@ def main():
248237 commit_cves_set = set (commit_cves )
249238 if not commit_cves_set .issubset (ticket_cves ):
250239 missing_in_ticket = commit_cves_set - ticket_cves
251- issues_list .append ({
252- 'type' : 'error' ,
253- 'vuln_id' : vuln_id ,
254- 'message' : f"CVE mismatch - Commit has { ', ' .join (sorted (missing_in_ticket ))} but VULN ticket does not"
255- })
240+ issues_list .append (
241+ {
242+ "type" : "error" ,
243+ "vuln_id" : vuln_id ,
244+ "message" : f"CVE mismatch - Commit has { ', ' .join (sorted (missing_in_ticket ))} but VULN ticket does not" ,
245+ }
246+ )
256247 if not ticket_cves .issubset (commit_cves_set ):
257248 missing_in_commit = ticket_cves - commit_cves_set
258- issues_list .append ({
259- 'type' : 'warning' ,
260- 'vuln_id' : vuln_id ,
261- 'message' : f"VULN ticket has { ', ' .join (sorted (missing_in_commit ))} but commit does not"
262- })
249+ issues_list .append (
250+ {
251+ "type" : "warning" ,
252+ "vuln_id" : vuln_id ,
253+ "message" : f"VULN ticket has { ', ' .join (sorted (missing_in_commit ))} but commit does not" ,
254+ }
255+ )
263256 elif commit_cves and not ticket_cves :
264- issues_list .append ({
265- 'type' : 'warning' ,
266- 'vuln_id' : vuln_id ,
267- 'message' : f"Commit has CVEs { ', ' .join (sorted (commit_cves ))} but VULN ticket has no CVEs"
268- })
257+ issues_list .append (
258+ {
259+ "type" : "warning" ,
260+ "vuln_id" : vuln_id ,
261+ "message" : f"Commit has CVEs { ', ' .join (sorted (commit_cves ))} but VULN ticket has no CVEs" ,
262+ }
263+ )
269264 elif ticket_cves and not commit_cves :
270- issues_list .append ({
271- 'type' : 'warning' ,
272- 'vuln_id' : vuln_id ,
273- 'message' : f"VULN ticket has CVEs { ', ' .join (sorted (ticket_cves ))} but commit has no CVEs"
274- })
265+ issues_list .append (
266+ {
267+ "type" : "warning" ,
268+ "vuln_id" : vuln_id ,
269+ "message" : f"VULN ticket has CVEs { ', ' .join (sorted (ticket_cves ))} but commit has no CVEs" ,
270+ }
271+ )
275272
276273 # Check ticket status
277274 status = issue .fields .status .name
278275 if status != "In Progress" :
279- issues_list .append ({
280- 'type' : 'error' ,
281- 'vuln_id' : vuln_id ,
282- 'message' : f"Status is '{ status } ', expected 'In Progress'"
283- })
276+ issues_list .append (
277+ {
278+ "type" : "error" ,
279+ "vuln_id" : vuln_id ,
280+ "message" : f"Status is '{ status } ', expected 'In Progress'" ,
281+ }
282+ )
284283
285284 # Check if time is logged
286285 time_spent = issue .fields .timespent
287286 if not time_spent or time_spent == 0 :
288- issues_list .append ({
289- 'type' : 'warning' ,
290- 'vuln_id' : vuln_id ,
291- 'message' : 'No time logged - please log time manually'
292- })
287+ issues_list .append (
288+ {
289+ "type" : "warning" ,
290+ "vuln_id" : vuln_id ,
291+ "message" : "No time logged - please log time manually" ,
292+ }
293+ )
293294
294295 # Check if LTS product matches merge target branch
295296 if lts_product and lts_product in release_map :
@@ -298,39 +299,43 @@ def main():
298299 lts_match = True
299300 else :
300301 lts_match = False
301- issues_list .append ({
302- 'type' : 'error' ,
303- 'vuln_id' : vuln_id ,
304- 'message' : f"LTS product '{ lts_product } ' expects branch '{ expected_branch } ', but merge target is '{ args .merge_target } '"
305- })
302+ issues_list .append (
303+ {
304+ "type" : "error" ,
305+ "vuln_id" : vuln_id ,
306+ "message" : f"LTS product '{ lts_product } ' expects branch '{ expected_branch } ', but merge target is '{ args .merge_target } '" ,
307+ }
308+ )
306309 else :
307- issues_list .append ({
308- 'type' : 'error' ,
309- 'vuln_id' : vuln_id ,
310- 'message' : f"LTS product '{ lts_product } ' not found in release_map"
311- })
310+ issues_list .append (
311+ {
312+ "type" : "error" ,
313+ "vuln_id" : vuln_id ,
314+ "message" : f"LTS product '{ lts_product } ' not found in release_map" ,
315+ }
316+ )
312317
313318 except Exception as e :
314- issues_list .append ({
315- ' type' : ' error' ,
316- 'vuln_id' : vuln_id ,
317- 'message' : f"Failed to retrieve ticket: { e } "
318- })
319-
320- commits_data . append ({
321- 'sha' : sha ,
322- 'summary' : summary ,
323- 'header' : header ,
324- 'full_message' : commit_msg ,
325- 'vuln_tickets' : vuln_tickets ,
326- 'lts_match' : lts_match ,
327- 'issues' : issues_list
328- } )
319+ issues_list .append (
320+ { " type" : " error" , "vuln_id" : vuln_id , "message" : f"Failed to retrieve ticket: { e } " }
321+ )
322+
323+ commits_data . append (
324+ {
325+ "sha" : sha ,
326+ "summary" : summary ,
327+ "header" : header ,
328+ "full_message" : commit_msg ,
329+ "vuln_tickets" : vuln_tickets ,
330+ "lts_match" : lts_match ,
331+ "issues" : issues_list ,
332+ }
333+ )
329334
330335 # Print formatted results
331336 print ("\n ## JIRA PR Check Results\n " )
332337
333- commits_with_issues = [c for c in commits_data if c [' issues' ]]
338+ commits_with_issues = [c for c in commits_data if c [" issues" ]]
334339 has_errors = False
335340
336341 if commits_with_issues :
@@ -341,8 +346,8 @@ def main():
341346 print (f"**Summary:** { commit ['summary' ]} \n " )
342347
343348 # Group issues by type
344- errors = [i for i in commit [' issues' ] if i [' type' ] == ' error' ]
345- warnings = [i for i in commit [' issues' ] if i [' type' ] == ' warning' ]
349+ errors = [i for i in commit [" issues" ] if i [" type" ] == " error" ]
350+ warnings = [i for i in commit [" issues" ] if i [" type" ] == " warning" ]
346351
347352 if errors :
348353 has_errors = True
@@ -372,6 +377,5 @@ def main():
372377 return jira , commits_data
373378
374379
375-
376380if __name__ == "__main__" :
377381 main ()
0 commit comments