@@ -208,8 +208,8 @@ def tag_priority(tag):
208208 else :
209209 return - 1
210210
211- def find_previous_build_commit (build_tag , build ):
212- """Find the previous build in an higher priority koji tag and return its commit ."""
211+ def find_previous_build (build_tag , build ):
212+ """Find the previous build of the package in an higher priority koji tag and return it ."""
213213 tagged = KOJI .listTagged (build_tag , package = build ['package_name' ], inherit = True )
214214 tagged = sorted (tagged , key = lambda t : (tag_priority (t ['tag_name' ]), - t ['build_id' ]))
215215 build_tag_priority = tag_priority (build_tag )
@@ -218,10 +218,27 @@ def find_previous_build_commit(build_tag, build):
218218 ]
219219 if not tagged :
220220 return None
221- previous_build = get_koji_build (tagged [0 ]['build_id' ])
222- if not previous_build .get ('source' ):
223- return None
224- return parse_source (previous_build ['source' ])[1 ]
221+ return get_koji_build (tagged [0 ]['build_id' ])
222+
223+ def released_tags (build_tag ):
224+ """Return the tags holding the released builds of the version, e.g. v8.3-updates and v8.3-base."""
225+ version = build_tag .split ('-' )[0 ][1 :] # v8.3-candidates -> 8.3
226+ return [] if not version else [f'v{ version } -updates' , f'v{ version } -base' ]
227+
228+ def find_released_build (build_tag , package_name ):
229+ """Find the newest released build of the package in the updates and base tags, updates first.
230+
231+ Some versions may not have an updates or base tag yet, which koji reports as an error, so a
232+ missing tag is treated as an empty one.
233+ """
234+ for tag in released_tags (build_tag ):
235+ try :
236+ tagged = KOJI .listTagged (tag , package = package_name )
237+ except koji .GenericError :
238+ continue
239+ if tagged :
240+ return get_koji_build (max (tagged , key = lambda t : t ['build_id' ])['build_id' ])
241+ return None
225242
226243def find_commits (gh , repo , start_sha , end_sha ) -> list [Commit ]:
227244 """
@@ -377,6 +394,7 @@ def get_plane_issues_with_milestones(plane_token):
377394 '--package' , '-p' , dest = 'packages' , help = "The packages to include in the report" , action = 'append' , default = []
378395)
379396parser .add_argument ('--re-cache' , help = "Refresh the cache" , action = 'store_true' )
397+ parser .add_argument ('--json-output' , help = "Also write a machine readable report in json format to this path" )
380398args = parser .parse_args ()
381399
382400CACHE = diskcache .Cache (args .cache )
@@ -411,6 +429,14 @@ def get_plane_issues_with_milestones(plane_token):
411429with urlopen ('https://github.com/xcp-ng/xcp/raw/refs/heads/master/scripts/rpm_owners/packages.json' ) as f :
412430 PACKAGES = json .load (f )
413431
432+ report_data = {
433+ 'generated_at' : started_at .isoformat (),
434+ 'generated_info' : args .generated_info ,
435+ 'warnings' : {'plane' : not issues , 'github' : not GITHUB },
436+ 'error' : None ,
437+ 'tags' : [],
438+ }
439+
414440with io .StringIO () as out :
415441 print_header (out )
416442 if not issues :
@@ -424,6 +450,8 @@ def get_plane_issues_with_milestones(plane_token):
424450 KOJI = koji .ClientSession ('https://kojihub.xcp-ng.org' , config )
425451 KOJI .ssl_login (config ['cert' ], None , config ['serverca' ])
426452 for tag in tags :
453+ tag_data = {'tag' : tag , 'builds' : []}
454+ report_data ['tags' ].append (tag_data )
427455 tag_history = dict (
428456 (tl ['build_id' ], tl ['create_ts' ])
429457 for tl in KOJI .queryHistory (tag = tag , active = True )['tag_listing' ]
@@ -436,7 +464,11 @@ def get_plane_issues_with_milestones(plane_token):
436464 build = get_koji_build (tagged ['build_id' ])
437465 prs : list [PullRequest ] = []
438466 maintained_by = None
439- previous_build_sha = find_previous_build_commit (tag , build )
467+ previous_build = find_previous_build (tag , build )
468+ previous_build_sha = None
469+ if previous_build is not None and previous_build .get ('source' ) is not None :
470+ previous_build_sha = parse_source (previous_build ['source' ])[1 ]
471+ released_build = find_released_build (tag , tagged ['package_name' ])
440472 if build ['source' ] is not None :
441473 (repo , sha ) = parse_source (build ['source' ])
442474 prs = find_pull_requests (repo , sha , previous_build_sha )
@@ -446,12 +478,34 @@ def get_plane_issues_with_milestones(plane_token):
446478 print_table_line (
447479 temp_out , tagged ['nvr' ], build_url , build_issues , tagged ['owner_name' ], prs , maintained_by
448480 )
481+ tag_data ['builds' ].append ({
482+ 'nvr' : tagged ['nvr' ],
483+ 'previous_nvr' : released_build ['nvr' ] if released_build is not None else None ,
484+ 'package' : tagged ['package_name' ],
485+ 'url' : build_url ,
486+ 'built_by' : tagged ['owner_name' ],
487+ 'maintained_by' : maintained_by ,
488+ 'issues' : [{
489+ 'sequence_id' : i ['sequence_id' ],
490+ 'url' : f'https://project.vates.tech/vates-global/browse/XCPNG-{ i ["sequence_id" ]} /' ,
491+ 'milestones' : sorted (set (i ['milestones' ])),
492+ } for i in build_issues ],
493+ 'pull_requests' : [{
494+ 'number' : pr .number ,
495+ 'title' : pr .title ,
496+ 'url' : pr .html_url ,
497+ 'linked' : issues_have_link (build_issues , pr .html_url ),
498+ } for pr in prs ],
499+ 'build_linked' : issues_have_link (build_issues , build_url ),
500+ })
449501 print_table_footer (temp_out )
450502 out .write (temp_out .getvalue ())
451503 except koji .GenericError :
504+ report_data ['error' ] = 'koji'
452505 print_koji_error (out )
453506 raise
454507 except Exception :
508+ report_data ['error' ] = 'unknown'
455509 print_generic_error (out )
456510 raise
457511 finally :
@@ -460,3 +514,7 @@ def get_plane_issues_with_milestones(plane_token):
460514 # write the actual output at once, in order to avoid a blank page during the processing
461515 with open (args .output , 'w' ) as f :
462516 f .write (out .getvalue ())
517+
518+ if args .json_output :
519+ with open (args .json_output , 'w' ) as f :
520+ json .dump (report_data , f , indent = 2 )
0 commit comments