3
3
import argparse
4
4
import os
5
5
import shutil
6
+ import json
6
7
import subprocess
7
8
import sys
8
9
import tempfile
9
10
import time
11
+ import rpm
10
12
from multiprocessing import Process
11
13
12
14
from dataclasses import dataclass
@@ -24,11 +26,79 @@ class DiffBuildTarget:
24
26
id : str
25
27
dir : str
26
28
meta : dict
29
+ commitmeta : dict
27
30
28
31
@staticmethod
29
32
def from_build (builds , build , arch ):
30
33
return DiffBuildTarget (build , builds .get_build_dir (build , arch ),
31
- builds .get_build_meta (build , arch ))
34
+ builds .get_build_meta (build , arch ),
35
+ builds .get_build_commitmeta (build , arch ))
36
+
37
+
38
+ class PackageDiffType (IntEnum ):
39
+ ADD = 0
40
+ REMOVE = 1
41
+ UPGRADE = 2
42
+ DOWNGRADE = 3
43
+
44
+
45
+ @dataclass
46
+ class Package :
47
+ name : str
48
+ epoch : int
49
+ v : str # Version
50
+ r : str # Release
51
+ a : str # Architecture
52
+
53
+ def evr (self ):
54
+ epoch_string = "" if self .epoch == "0" else f"{ self .epoch } :"
55
+ return f"{ epoch_string } { self .v } -{ self .r } "
56
+
57
+ # Compare versions
58
+ # rc > 0 -> Newer; rc == 0 -> Same; rc < 0 -> Older
59
+ @staticmethod
60
+ def vercmp (from_pkg , to_pkg ):
61
+ return rpm .labelCompare (
62
+ (to_pkg .epoch , to_pkg .v , to_pkg .r ),
63
+ (from_pkg .epoch , from_pkg .v , from_pkg .r )
64
+ )
65
+
66
+ # Return a data sctructure representing a diff entry from the
67
+ # rpm-ostree db diff --json output.
68
+ # "pkgdiff" : [
69
+ # [
70
+ # "NetworkManager",
71
+ # 2,
72
+ # {
73
+ # "PreviousPackage" : [
74
+ # "NetworkManager",
75
+ # "1:1.52.0-5.el9_6",
76
+ # "x86_64"
77
+ # ],
78
+ # "NewPackage" : [
79
+ # "NetworkManager",
80
+ # "1:1.52.0-7.el9_6",
81
+ # "x86_64"
82
+ # ]
83
+ # }
84
+ # ],
85
+ @staticmethod
86
+ def to_json_diff_entry (difftype : PackageDiffType , from_pkg , to_pkg ):
87
+ name = from_pkg .name if from_pkg else to_pkg .name
88
+ change = ""
89
+ match difftype :
90
+ case PackageDiffType .ADD :
91
+ change = {"NewPackage" : [to_pkg .name , to_pkg .evr (), to_pkg .a ]}
92
+ case PackageDiffType .REMOVE :
93
+ change = {"PreviousPackage" : [from_pkg .name , from_pkg .evr (), from_pkg .a ]}
94
+ case PackageDiffType .UPGRADE | PackageDiffType .DOWNGRADE :
95
+ change = {
96
+ "PreviousPackage" : [from_pkg .name , from_pkg .evr (), from_pkg .a ],
97
+ "NewPackage" : [to_pkg .name , to_pkg .evr (), to_pkg .a ]
98
+ }
99
+ case _:
100
+ raise
101
+ return [name , difftype , change ]
32
102
33
103
34
104
class OSTreeImport (IntEnum ):
@@ -167,6 +237,89 @@ def diff_rpms_rpm_ostree(diff_from, diff_to, json):
167
237
runcmd (cmd )
168
238
169
239
240
+ def diff_rpms_commitmeta_json (diff_from , diff_to ):
241
+ diff_rpms_commitmeta (diff_from , diff_to , json_output = True )
242
+
243
+
244
+ def diff_rpms_commitmeta_no_json (diff_from , diff_to ):
245
+ diff_rpms_commitmeta (diff_from , diff_to , json_output = False )
246
+
247
+
248
+ def diff_rpms_commitmeta (diff_from , diff_to , json_output = False ):
249
+ """
250
+ Diff the commitmeta.json files between two builds.
251
+ """
252
+ from_meta = diff_from .meta
253
+ to_meta = diff_to .meta
254
+ from_commitmeta = diff_from .commitmeta
255
+ to_commitmeta = diff_to .commitmeta
256
+
257
+ # Gather info about upgraded, downgraded, added, removed RPMS
258
+ from_rpms = {pkg [0 ]: Package (* pkg ) for pkg in from_commitmeta .get ('rpmostree.rpmdb.pkglist' , [])}
259
+ to_rpms = {pkg [0 ]: Package (* pkg ) for pkg in to_commitmeta .get ('rpmostree.rpmdb.pkglist' , [])}
260
+
261
+ added_rpms = set (to_rpms .keys ()) - set (from_rpms .keys ())
262
+ removed_rpms = set (from_rpms .keys ()) - set (to_rpms .keys ())
263
+ common_rpms = set (from_rpms .keys ()) & set (to_rpms .keys ())
264
+
265
+ upgraded_rpms = []
266
+ downgraded_rpms = []
267
+ for name in common_rpms :
268
+ from_pkg = from_rpms [name ]
269
+ to_pkg = to_rpms [name ]
270
+ rc = Package .vercmp (from_pkg , to_pkg )
271
+ if rc > 0 :
272
+ upgraded_rpms .append ((name , from_pkg , to_pkg ))
273
+ elif rc == 0 :
274
+ pass # They are equal versions
275
+ elif rc < 0 :
276
+ downgraded_rpms .append ((name , from_pkg , to_pkg ))
277
+
278
+ if json_output :
279
+ diff = {
280
+ "ostree-commit-from" : from_meta .get ('ostree-commit' ),
281
+ "ostree-commit-to" : to_meta .get ('ostree-commit' ),
282
+ "pkgdiff" : [],
283
+ "advisories" : []
284
+ }
285
+ for name in sorted (added_rpms ):
286
+ diff ['pkgdiff' ].append (Package .to_json_diff_entry (PackageDiffType .ADD , from_pkg = None , to_pkg = to_rpms [name ]))
287
+ for name in sorted (removed_rpms ):
288
+ diff ['pkgdiff' ].append (Package .to_json_diff_entry (PackageDiffType .REMOVE , from_pkg = from_rpms [name ], to_pkg = None ))
289
+ for name , from_pkg , to_pkg in sorted (upgraded_rpms ):
290
+ diff ['pkgdiff' ].append (Package .to_json_diff_entry (PackageDiffType .UPGRADE , from_pkg = from_pkg , to_pkg = to_pkg ))
291
+ for name , from_pkg , to_pkg in sorted (downgraded_rpms ):
292
+ diff ['pkgdiff' ].append (Package .to_json_diff_entry (PackageDiffType .DOWNGRADE , from_pkg = from_pkg , to_pkg = to_pkg ))
293
+
294
+ # Only add advisory info for the JSON output
295
+ from_advisories = {adv [0 ]: adv for adv in from_commitmeta .get ('rpmostree.advisories' , [])}
296
+ to_advisories = {adv [0 ]: adv for adv in to_commitmeta .get ('rpmostree.advisories' , [])}
297
+ added_advisories = set (to_advisories .keys ()) - set (from_advisories .keys ())
298
+ for name in added_advisories :
299
+ diff ['advisories' ].append (to_advisories [name ])
300
+ # Dump the JSON to stdout
301
+ print (json .dumps (diff , indent = 2 ))
302
+ else :
303
+ print (f"ostree diff commit from: { diff_from .id } ({ from_meta .get ('ostree-commit' )} )" )
304
+ print (f"ostree diff commit to: { diff_to .id } ({ to_meta .get ('ostree-commit' )} )" )
305
+ if upgraded_rpms :
306
+ print ("Upgraded:" )
307
+ for name , from_pkg , to_pkg in sorted (upgraded_rpms ):
308
+ print (f" { name } { from_pkg .evr ()} -> { to_pkg .evr ()} " )
309
+ if downgraded_rpms :
310
+ print ("Downgraded:" )
311
+ for name , from_pkg , to_pkg in sorted (downgraded_rpms ):
312
+ print (f" { name } { from_pkg .evr ()} -> { to_pkg .evr ()} " )
313
+ if removed_rpms :
314
+ print ("Removed:" )
315
+ for name in sorted (removed_rpms ):
316
+ print (f" { name } -{ from_rpms [name ].evr ()} " )
317
+ if added_rpms :
318
+ print ("Added:" )
319
+ for name in sorted (added_rpms ):
320
+ print (f" { name } -{ to_rpms [name ].evr ()} " )
321
+
322
+
170
323
def diff_ostree_ls (diff_from , diff_to ):
171
324
commit_from = diff_from .meta ['ostree-commit' ]
172
325
commit_to = diff_to .meta ['ostree-commit' ]
@@ -476,6 +629,10 @@ DIFFERS = [
476
629
needs_ostree = OSTreeImport .FULL , function = diff_rpms_rpm_ostree_no_json ),
477
630
Differ ("rpms-json" , "Diff RPMs, output JSON" ,
478
631
needs_ostree = OSTreeImport .FULL , function = diff_rpms_rpm_ostree_json ),
632
+ Differ ("rpms-commitmeta" , "Diff rpms from commitmeta.json" ,
633
+ needs_ostree = OSTreeImport .NO , function = diff_rpms_commitmeta_no_json ),
634
+ Differ ("rpms-json-commitmeta" , "Diff RPMS & Advisories from commitmeta.json, output JSON" ,
635
+ needs_ostree = OSTreeImport .NO , function = diff_rpms_commitmeta_json ),
479
636
Differ ("source-control" , "Diff config and COSA input commits" ,
480
637
needs_ostree = OSTreeImport .NO , function = diff_source_control ),
481
638
Differ ("ostree-ls" , "Diff OSTree contents using 'ostree diff'" ,
0 commit comments