3232from dnf .i18n import _ , exact_width
3333from dnf .pycomp import unicode
3434
35+ import sys
36+ import json
3537
3638def _maxlen (iterable ):
3739 """Return maximum length of items in a non-empty iterable."""
@@ -102,6 +104,9 @@ def set_argparser(parser):
102104 parser .add_argument ("--with-bz" , dest = 'with_bz' , default = False ,
103105 action = 'store_true' ,
104106 help = _ ('show only advisories with bugzilla reference' ))
107+ parser .add_argument ("--json" , dest = 'json' , default = False ,
108+ action = 'store_true' ,
109+ help = _ ('Display in JSON format.' ))
105110 parser .add_argument ('spec' , nargs = '*' , metavar = 'SPEC' ,
106111 choices = cmds , default = cmds [0 ],
107112 action = OptionParser .PkgNarrowCallback ,
@@ -157,6 +162,10 @@ def configure(self):
157162 else :
158163 self .opts .spec .insert (0 , spec )
159164
165+ # Keep quiet when dumping JSON output
166+ if self .opts .json :
167+ self .cli .redirect_logger (stdout = sys .maxsize , stderr = sys .maxsize )
168+
160169 if self .opts .advisory :
161170 self .opts .spec .extend (self .opts .advisory )
162171
@@ -327,10 +336,10 @@ def type2label(typ, sev):
327336 elif ref .type == hawkey .REFERENCE_CVE and not self .opts .with_cve :
328337 continue
329338 nevra_inst_dict .setdefault ((nevra , installed , advisory .updated ), dict ())[ref .id ] = (
330- advisory .type , advisory .severity )
339+ advisory .type , advisory .severity )
331340 else :
332341 nevra_inst_dict .setdefault ((nevra , installed , advisory .updated ), dict ())[advisory .id ] = (
333- advisory .type , advisory .severity )
342+ advisory .type , advisory .severity )
334343
335344 advlist = []
336345 # convert types to labels, find max len of advisory IDs and types
@@ -339,15 +348,88 @@ def type2label(typ, sev):
339348 nw = max (nw , len (nevra ))
340349 for aid , atypesev in id2type .items ():
341350 idw = max (idw , len (aid ))
351+ typ , sev = atypesev
342352 label = type2label (* atypesev )
353+ # use dnf5 style for JSON output
354+ atype = self .TYPE2LABEL .get (typ , _ ('unspecified' ))
355+ asev = self .SECURITY2LABEL .get (sev , _ ('None' ))
356+ asev = asev .split ("/" )[0 ].strip ()
343357 tlw = max (tlw , len (label ))
344- advlist .append ((inst2mark (inst ), aid , label , nevra , aupdated ))
345-
346- for (inst , aid , label , nevra , aupdated ) in advlist :
347- if self .base .conf .verbose :
348- print ('%s%-*s %-*s %-*s %s' % (inst , idw , aid , tlw , label , nw , nevra , aupdated ))
349- else :
350- print ('%s%-*s %-*s %s' % (inst , idw , aid , tlw , label , nevra ))
358+ advlist .append ((inst2mark (inst ), aid , label , atype , asev , nevra , aupdated ))
359+ if self .opts .json :
360+ dtlst = []
361+ for (inst , aid , label , atype , asev , nevra , aupdated ) in advlist :
362+ dtlst .append (
363+ {
364+ "name" : aid ,
365+ "type" : atype ,
366+ "severity" : asev ,
367+ "nevra" : nevra ,
368+ "buildtime" : aupdated ,
369+ }
370+ )
371+ print (json .dumps (dtlst , default = str , indent = 2 ))
372+ else :
373+ for (inst , aid , label , atype , asev , nevra , aupdated ) in advlist :
374+ if self .base .conf .verbose :
375+ print ('%s%-*s %-*s %-*s %s' % (inst , idw , aid , tlw , label , nw , nevra , aupdated ))
376+ else :
377+ print ('%s%-*s %-*s %s' % (inst , idw , aid , tlw , label , nevra ))
378+
379+ def _process_advisory (self , advisory ):
380+ """Convert DNF advisory object directly to desired format."""
381+ advisory_id = getattr (advisory , 'id' , None )
382+
383+ package_list = []
384+ for pkg in getattr (advisory , 'packages' , []):
385+ if not getattr (pkg , 'name' , None ):
386+ continue
387+ pkg_info = {
388+ 'name' : getattr (pkg , 'name' , None ),
389+ 'evr' : getattr (pkg , 'evr' , None ),
390+ 'arch' : getattr (pkg , 'arch' , None ),
391+ }
392+ pkg_str = f"{ pkg_info .get ('name' )} -{ pkg_info .get ('evr' )} "
393+ if pkg_info .get ('arch' ):
394+ pkg_str += f".{ pkg_info .get ('arch' )} "
395+ package_list .append (pkg_str )
396+
397+ REFERENCE_TYPES = {0 : 'unknown' , 1 : 'bugzilla' , 2 : 'cve' , 3 : 'vendor' , 4 : 'security' }
398+ references = []
399+ for ref in getattr (advisory , 'references' , []):
400+ ref_dict = {
401+ 'Title' : getattr (ref , 'title' , None ),
402+ 'Id' : getattr (ref , 'id' , None ),
403+ 'Type' : REFERENCE_TYPES .get (getattr (ref , 'type' , 0 ), 'unknown' ),
404+ 'Url' : getattr (ref , 'href' , None ) or getattr (ref , 'url' , None ) or getattr (ref , 'link' , None )
405+ }
406+ ref_dict = {k : v for k , v in ref_dict .items () if v is not None }
407+ references .append (ref_dict )
408+
409+ result = {
410+ advisory_id : {
411+ 'Name' : advisory_id ,
412+ 'Title' : getattr (advisory , 'title' , None ),
413+ 'Severity' : getattr (advisory , 'severity' , 'None' ),
414+ 'Type' : self .TYPE2LABEL .get (getattr (advisory , 'type' ), _ ('unspecified' )),
415+ 'Status' : getattr (advisory , 'status' , None ),
416+ 'Vendor' : (getattr (advisory , 'vendor' , None ) \
417+ or getattr (advisory , 'author' , None ) \
418+ or getattr (advisory , 'from' , None )),
419+ 'Issued' : getattr (advisory , 'updated' , '' ).strftime ("%Y-%m-%d %H:%M:%S" )
420+ if getattr (advisory , 'updated' , None )
421+ else None ,
422+ 'Description' : getattr (advisory , 'description' , None ),
423+ 'Message' : '' ,
424+ 'Rights' : getattr (advisory , 'rights' , None ),
425+ 'references' : references ,
426+ 'collections' : {
427+ 'packages' : package_list
428+ }
429+ }
430+ }
431+
432+ return result
351433
352434
353435 def display_info (self , apkg_adv_insts ):
@@ -398,8 +480,14 @@ def advisory2info(advisory, installed):
398480 lines .append ('%*s%s: %s' % (key_padding , "" , key , line ))
399481 return '\n ' .join (lines )
400482
483+ dt_advisories = {}
401484 advisories = set ()
402485 for apkg , advisory , installed in apkg_adv_insts :
403- advisories .add (advisory2info (advisory , installed ))
486+ formatted_attributes = advisory2info (advisory , installed )
487+ advisories .add (formatted_attributes )
488+ dt_advisories .update (self ._process_advisory (advisory ))
404489
405- print ("\n \n " .join (sorted (advisories , key = lambda x : x .lower ())))
490+ if self .opts .json :
491+ print (json .dumps (dt_advisories , default = str , indent = 2 ))
492+ else :
493+ print ("\n \n " .join (sorted (advisories , key = lambda x : x .lower ())))
0 commit comments