45
45
- Update find_comp_in_bom to return the matching URL instead of
46
46
True/False
47
47
- Track unique BOM matches by tracking the matched component URL
48
- returned by find_comp_in_com
48
+ returned by find_comp_in_bom
49
49
- Track the count of skipped items from the SPDX
50
50
- Make the unique package tracking more accurate - do not include skipped items
51
51
- Create fall-through matching. First check BD component, then the purl info
52
52
(rather than only checking the purl)
53
+ 1.4 2023-11-21 - Check the component-import-events API for improved BOM
54
+ component searching accuracy
53
55
54
56
Requirements
55
57
- python3 version 3.8 or newer recommended
@@ -268,7 +270,8 @@ def check_for_existing_scan(projver):
268
270
# inside the json body)
269
271
# proj_version_url: Project version url
270
272
#
271
- # Returns on success. Errors will result in fatal exit.
273
+ # Returns summaries_url on success (used for processing the import events later)
274
+ # Errors will result in fatal exit.
272
275
def poll_for_sbom_complete (sbom_name , proj_version_url ):
273
276
retries = MAX_RETRIES
274
277
sleep_time = SLEEP
@@ -382,7 +385,7 @@ def poll_for_sbom_complete(sbom_name, proj_version_url):
382
385
poll_notifications_for_success (cl_url , proj_version_url , summaries_url )
383
386
384
387
# Any errors above already resulted in fatal exit
385
- return
388
+ return summaries_url
386
389
387
390
# Upload provided SBOM file to Black Duck
388
391
# Inputs:
@@ -472,13 +475,21 @@ def find_comp_id_in_kb(comp, ver):
472
475
473
476
return kb_match
474
477
478
+ # Locate component name + version in component-import-events
479
+ # Returns matched name+version on success, None on failure
480
+ def find_comp_import_events (match_dict , compname , compver ):
481
+ key = compname + compver
482
+ if key in match_dict :
483
+ return match_dict [key ]
484
+ return None
485
+
475
486
# Locate component name + version in BOM
476
487
# Inputs:
477
488
# compname - Component name to locate
478
489
# compver - Component version to locate
479
490
# projver - Project version to locate component in BOM
480
491
#
481
- # Returns: Component match URL on success, None on failure
492
+ # Returns: Component name+version string on success, None on failure
482
493
def find_comp_in_bom (compname , compver , projver ):
483
494
have_match = False
484
495
num_match = 0
@@ -496,14 +507,14 @@ def find_comp_in_bom(compname, compver, projver):
496
507
# The BD API search is inexact. Force our match to be precise.
497
508
continue
498
509
if compver == "UNKNOWN" :
499
- # We did not have a version specified in the first place
500
- return comp ['component' ]
510
+ # No version specified in SPDX, so treat it as a match
511
+ return comp ['componentName' ] + "NOVERSION"
501
512
# Check component name + version name
502
513
try :
503
514
if comp ['componentVersionName' ].lower () == compver .lower ():
504
- return comp ['componentVersion ' ]
515
+ return comp ['componentName' ] + comp [ 'componentVersionName ' ]
505
516
except :
506
- # Handle situation where it's missing the version name for some reason
517
+ # Handle situation where it's missing the version name
507
518
print (f"comp { compname } in BOM has no version!" )
508
519
return None
509
520
return None
@@ -641,6 +652,34 @@ def add_to_sbom(proj_version_url, comp_ver_url):
641
652
logging .error (f"Status code: { response .status_code } " )
642
653
sys .exit (1 )
643
654
655
+ # Get matched component data from component import events
656
+ # Input: Summaries URL
657
+ # Output: Dictionary containing components added to BOM
658
+ # Key=<import component name> + <import component version>
659
+ # Value=<matched name> + <matched version>
660
+ def get_matched_comps (summaries_url ):
661
+ match_dict = {} # dictionary to be returned
662
+
663
+ summary_data = bd .get_json (summaries_url )
664
+ links = summary_data ['_meta' ]['links' ]
665
+ for link in links :
666
+ # Locate the component-import-events link
667
+ if link ['rel' ] == "component-import-events" :
668
+ cie_link = link ['href' ]
669
+ break
670
+
671
+ # Only consider successful matches
672
+ params = {
673
+ 'filter' : ["eventName:component_mapping_succeeded" ]
674
+ }
675
+ for comp in bd .get_items (cie_link , params = params ):
676
+ key = comp ['importComponentName' ]+ comp ['importComponentVersionName' ]
677
+ val = comp ['componentName' ]+ comp ['componentVersionName' ]
678
+ match_dict [key ] = val
679
+
680
+ return (match_dict )
681
+
682
+
644
683
def parse_command_args ():
645
684
parser = argparse .ArgumentParser (description = "Parse SPDX file and verify if component names are in current SBOM for given project-version" )
646
685
parser .add_argument ("--base-url" , required = True , help = "Hub server URL e.g. https://your.blackduck.url" )
@@ -710,7 +749,9 @@ def import_sbom(bdobj, projname, vername, spdxfile, outfile=None, \
710
749
upload_sbom_file (spdxfile , projname , vername )
711
750
712
751
# Wait for scan completion. Will exit if it fails.
713
- poll_for_sbom_complete (document .creation_info .name , proj_version_url )
752
+ summaries_url = poll_for_sbom_complete (document .creation_info .name , proj_version_url )
753
+ # Collect the matched component data for later processing
754
+ match_dict = get_matched_comps (summaries_url )
714
755
715
756
# Open unmatched component file to save name, spdxid, version, and
716
757
# origin/purl for later in json format
@@ -812,17 +853,30 @@ def import_sbom(bdobj, projname, vername, spdxfile, outfile=None, \
812
853
else :
813
854
print (f" No KB match for { package .name } { package .version } " )
814
855
else :
815
- # No external references field was provide
856
+ # No external references field was provided
816
857
nopurl += 1
817
858
print (f" No pURL provided for { package .name } { package .version } " )
818
859
819
- bom_comp = find_comp_in_bom (matchname , matchver , version )
860
+ # find_comp_import_events checks the imported name-version
861
+ bom_comp = find_comp_import_events (match_dict , package .name , package .version )
820
862
if bom_comp :
863
+ # bom_comp is the matched comp/ver string
821
864
bom_packages [bom_comp ] = bom_packages .get (bom_comp , 0 ) + 1
822
865
packages [matchname + matchver ] = packages .get (matchname + matchver , 0 ) + 1
823
866
bom_matches += 1
824
- print (f" Found component in BOM : { matchname } { matchver } " )
867
+ print (f" Found component in bom import-events : { matchname } { matchver } " )
825
868
continue
869
+ else :
870
+ # Next look for the matchname-matchver in the BOM
871
+ # component search. The component name-version may have been
872
+ # updated above to reflect the pURL or KB matched name.
873
+ bom_comp = find_comp_in_bom (matchname , matchver , version )
874
+ if bom_comp :
875
+ bom_packages [bom_comp ] = bom_packages .get (bom_comp , 0 ) + 1
876
+ packages [matchname + matchver ] = packages .get (matchname + matchver , 0 ) + 1
877
+ bom_matches += 1
878
+ print (f" Found component in BOM: { matchname } { matchver } " )
879
+ continue
826
880
827
881
# If we've gotten this far, the package is not in the BOM.
828
882
# Now we need to figure out:
0 commit comments