39
39
- Check if project every had a "non-SBOM" scan and exit if so
40
40
- Fix some invalid sort parameter formatting
41
41
- Limit notification checking to last 24 hours
42
+ 1.3 2023-11-14 - Force encoding utf8 when opening file
43
+ - Use the component URL from API call in the version query - resolves
44
+ some situations where the KB version lookup fails
45
+ - Update find_comp_in_bom to return the matching URL instead of
46
+ True/False
47
+ - Track unique BOM matches by tracking the matched component URL
48
+ returned by find_comp_in_com
49
+ - Track the count of skipped items from the SPDX
50
+ - Make the unique package tracking more accurate - do not include skipped items
51
+ - Create fall-through matching. First check BD component, then the purl info
52
+ (rather than only checking the purl)
42
53
43
54
Requirements
44
-
45
55
- python3 version 3.8 or newer recommended
46
56
- The following packages are used by the script and should be installed
47
57
prior to use:
@@ -184,7 +194,7 @@ def spdx_validate(document):
184
194
# Returns MIME type to provide to scan API
185
195
# Input: filename to check
186
196
def get_sbom_mime_type (filename ):
187
- with open (filename , 'r' ) as f :
197
+ with open (filename , 'r' , encoding = "utf8" ) as f :
188
198
data = f .readlines ()
189
199
content = " " .join (data )
190
200
if 'CycloneDX' in content :
@@ -448,8 +458,10 @@ def find_comp_id_in_kb(comp, ver):
448
458
kb_match ['versionName' ] = "UNKNOWN"
449
459
return kb_match
450
460
461
+ # Update the component url to match the one returned by API
462
+ comp_url = json_data ['_meta' ]['href' ]
451
463
try :
452
- json_data = bd .get_json (f"/api/components/ { comp } /versions/{ ver } " )
464
+ json_data = bd .get_json (f"{ comp_url } /versions/{ ver } " )
453
465
except :
454
466
# No component version match
455
467
return None
@@ -466,7 +478,7 @@ def find_comp_id_in_kb(comp, ver):
466
478
# compver - Component version to locate
467
479
# projver - Project version to locate component in BOM
468
480
#
469
- # Returns: True on success, False on failure
481
+ # Returns: Component match URL on success, None on failure
470
482
def find_comp_in_bom (compname , compver , projver ):
471
483
have_match = False
472
484
num_match = 0
@@ -485,16 +497,16 @@ def find_comp_in_bom(compname, compver, projver):
485
497
continue
486
498
if compver == "UNKNOWN" :
487
499
# We did not have a version specified in the first place
488
- return True
500
+ return comp [ 'component' ]
489
501
# Check component name + version name
490
502
try :
491
503
if comp ['componentVersionName' ].lower () == compver .lower ():
492
- return True
504
+ return comp [ 'componentVersion' ]
493
505
except :
494
506
# Handle situation where it's missing the version name for some reason
495
507
print (f"comp { compname } in BOM has no version!" )
496
- return False
497
- return False
508
+ return None
509
+ return None
498
510
499
511
# Verifies if a custom component and version already exist in the system.
500
512
#
@@ -718,8 +730,11 @@ def import_sbom(bdobj, projname, vername, spdxfile, outfile=None, \
718
730
package_count = 0
719
731
cust_comp_count = 0
720
732
cust_ver_count = 0
721
- # Used for tracking repeated package data
733
+ skip_count = 0
734
+ # Used for tracking repeated package data, not including skips
722
735
packages = {}
736
+ # Used for tracking unique BOM matches
737
+ bom_packages = {}
723
738
# Saved component data to write to file
724
739
comps_out = []
725
740
@@ -733,6 +748,7 @@ def import_sbom(bdobj, projname, vername, spdxfile, outfile=None, \
733
748
if package .name == "" :
734
749
# Strange case where the package name is empty. Skip it.
735
750
logging .warning ("WARNING: Skipping empty package name. Package info:" )
751
+ skip_count += 1
736
752
pprint (package )
737
753
continue
738
754
@@ -749,10 +765,6 @@ def import_sbom(bdobj, projname, vername, spdxfile, outfile=None, \
749
765
matchver = package .version
750
766
print (f"Processing SPDX package: { matchname } version: { matchver } ..." )
751
767
752
- # Tracking unique package name + version combos from spdx file
753
- # This is only used for debugging and stats purposes
754
- packages [matchname + matchver ] = packages .get (matchname + matchver , 0 ) + 1
755
-
756
768
kb_match = None
757
769
if package .external_references :
758
770
# Build dictionary of extrefs for easy access
@@ -762,11 +774,8 @@ def import_sbom(bdobj, projname, vername, spdxfile, outfile=None, \
762
774
reftype = ref .reference_type .lstrip ("LocationRef-" )
763
775
extrefs [reftype ] = ref .locator
764
776
765
- if "purl" in extrefs :
766
- # purl is the preferred lookup
767
- kb_match = find_comp_in_kb (extrefs ['purl' ])
768
- extref = extrefs ['purl' ]
769
- elif "BlackDuck-Component" in extrefs :
777
+ if "BlackDuck-Component" in extrefs :
778
+ # Prefer BD component lookup if available
770
779
compid = normalize_id (extrefs ['BlackDuck-Component' ])
771
780
try :
772
781
verid = normalize_id (extrefs ['BlackDuck-ComponentVersion' ])
@@ -776,8 +785,18 @@ def import_sbom(bdobj, projname, vername, spdxfile, outfile=None, \
776
785
# Lookup by KB ID
777
786
kb_match = find_comp_id_in_kb (compid , verid )
778
787
extref = extrefs ['BlackDuck-Component' ]
788
+ if not kb_match :
789
+ # BD comp lookup failed, so try purl instead
790
+ if "purl" in extrefs :
791
+ kb_match = find_comp_in_kb (extrefs ['purl' ])
792
+ extref = extrefs ['purl' ]
793
+ elif "purl" in extrefs :
794
+ # If no BD component details are available
795
+ kb_match = find_comp_in_kb (extrefs ['purl' ])
796
+ extref = extrefs ['purl' ]
779
797
elif "BlackDuck-Version" in extrefs :
780
798
# Skip BD project/versions. These occur in BD-generated BOMs.
799
+ skip_count += 1
781
800
print (f" Skipping BD project/version in BOM: { package .name } { package .version } " )
782
801
continue
783
802
else :
@@ -797,7 +816,10 @@ def import_sbom(bdobj, projname, vername, spdxfile, outfile=None, \
797
816
nopurl += 1
798
817
print (f" No pURL provided for { package .name } { package .version } " )
799
818
800
- if find_comp_in_bom (matchname , matchver , version ):
819
+ bom_comp = find_comp_in_bom (matchname , matchver , version )
820
+ if bom_comp :
821
+ bom_packages [bom_comp ] = bom_packages .get (bom_comp , 0 ) + 1
822
+ packages [matchname + matchver ] = packages .get (matchname + matchver , 0 ) + 1
801
823
bom_matches += 1
802
824
print (f" Found component in BOM: { matchname } { matchver } " )
803
825
continue
@@ -809,6 +831,7 @@ def import_sbom(bdobj, projname, vername, spdxfile, outfile=None, \
809
831
# - Do we need to add a version to an existing custom component?
810
832
not_in_bom += 1
811
833
print (f" Not present in BOM: { matchname } { matchver } " )
834
+ packages [matchname + matchver ] = packages .get (matchname + matchver , 0 ) + 1
812
835
813
836
# Missing component data to write to a file for reference
814
837
comp_data = {
@@ -862,16 +885,18 @@ def import_sbom(bdobj, projname, vername, spdxfile, outfile=None, \
862
885
print (f" SPDX packages processed: { package_count } " )
863
886
# package_count above could have repeated packages in it
864
887
print (f" Unique packages processed: { len (packages )} " )
888
+ print (f" Skipped: { skip_count } " )
865
889
print (f" Packages missing purl or KBID: { nopurl } " )
866
890
print (f" BOM matches: { bom_matches } " )
891
+ print (f" Unique BOM matches: { len (bom_packages )} " )
867
892
print (f" KB matches: { kb_matches } " )
868
893
print (f" Custom components created: { cust_comp_count } " )
869
894
print (f" Custom component versions created: { cust_ver_count } " )
870
895
print (f" Packages missing from BOM: { not_in_bom } " )
871
896
print (f" Custom components added to BOM: { cust_added_to_bom } " )
872
897
print (f" KB matches added to BOM: { kb_match_added_to_bom } " )
873
- #for debugging
874
898
#pprint(packages)
899
+ #pprint(bom_packages)
875
900
876
901
if __name__ == "__main__" :
877
902
sys .exit (spdx_main_parse_args ())
0 commit comments