Skip to content

Commit 6119ab0

Browse files
author
Yonghang Wang
committed
feat: Add JSON output for updateinfo
This is a backporting for the feature introduced to dnf5 by the following pull requests: - rpm-software-management/dnf5#1531 - rpm-software-management/dnf5#1970 The feature enables JSON format output for updateinfo command.
1 parent c1f8aee commit 6119ab0

File tree

1 file changed

+99
-11
lines changed

1 file changed

+99
-11
lines changed

dnf/cli/commands/updateinfo.py

Lines changed: 99 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232
from dnf.i18n import _, exact_width
3333
from dnf.pycomp import unicode
3434

35+
import sys
36+
import json
3537

3638
def _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+
dt_advisories.update(self._process_advisory(advisory))
487+
formatted_attributes = advisory2info(advisory, installed)
488+
advisories.add(formatted_attributes)
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

Comments
 (0)