4545 - Update find_comp_in_bom to return the matching URL instead of
4646 True/False
4747 - Track unique BOM matches by tracking the matched component URL
48- returned by find_comp_in_com
48+ returned by find_comp_in_bom
4949 - Track the count of skipped items from the SPDX
5050 - Make the unique package tracking more accurate - do not include skipped items
5151 - Create fall-through matching. First check BD component, then the purl info
5252 (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
5355
5456Requirements
5557- python3 version 3.8 or newer recommended
@@ -268,7 +270,8 @@ def check_for_existing_scan(projver):
268270# inside the json body)
269271# proj_version_url: Project version url
270272#
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.
272275def poll_for_sbom_complete (sbom_name , proj_version_url ):
273276 retries = MAX_RETRIES
274277 sleep_time = SLEEP
@@ -382,7 +385,7 @@ def poll_for_sbom_complete(sbom_name, proj_version_url):
382385 poll_notifications_for_success (cl_url , proj_version_url , summaries_url )
383386
384387 # Any errors above already resulted in fatal exit
385- return
388+ return summaries_url
386389
387390# Upload provided SBOM file to Black Duck
388391# Inputs:
@@ -472,13 +475,21 @@ def find_comp_id_in_kb(comp, ver):
472475
473476 return kb_match
474477
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+
475486# Locate component name + version in BOM
476487# Inputs:
477488# compname - Component name to locate
478489# compver - Component version to locate
479490# projver - Project version to locate component in BOM
480491#
481- # Returns: Component match URL on success, None on failure
492+ # Returns: Component name+version string on success, None on failure
482493def find_comp_in_bom (compname , compver , projver ):
483494 have_match = False
484495 num_match = 0
@@ -496,14 +507,14 @@ def find_comp_in_bom(compname, compver, projver):
496507 # The BD API search is inexact. Force our match to be precise.
497508 continue
498509 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"
501512 # Check component name + version name
502513 try :
503514 if comp ['componentVersionName' ].lower () == compver .lower ():
504- return comp ['componentVersion ' ]
515+ return comp ['componentName' ] + comp [ 'componentVersionName ' ]
505516 except :
506- # Handle situation where it's missing the version name for some reason
517+ # Handle situation where it's missing the version name
507518 print (f"comp { compname } in BOM has no version!" )
508519 return None
509520 return None
@@ -641,6 +652,34 @@ def add_to_sbom(proj_version_url, comp_ver_url):
641652 logging .error (f"Status code: { response .status_code } " )
642653 sys .exit (1 )
643654
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+
644683def parse_command_args ():
645684 parser = argparse .ArgumentParser (description = "Parse SPDX file and verify if component names are in current SBOM for given project-version" )
646685 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, \
710749 upload_sbom_file (spdxfile , projname , vername )
711750
712751 # 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 )
714755
715756 # Open unmatched component file to save name, spdxid, version, and
716757 # origin/purl for later in json format
@@ -812,17 +853,30 @@ def import_sbom(bdobj, projname, vername, spdxfile, outfile=None, \
812853 else :
813854 print (f" No KB match for { package .name } { package .version } " )
814855 else :
815- # No external references field was provide
856+ # No external references field was provided
816857 nopurl += 1
817858 print (f" No pURL provided for { package .name } { package .version } " )
818859
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 )
820862 if bom_comp :
863+ # bom_comp is the matched comp/ver string
821864 bom_packages [bom_comp ] = bom_packages .get (bom_comp , 0 ) + 1
822865 packages [matchname + matchver ] = packages .get (matchname + matchver , 0 ) + 1
823866 bom_matches += 1
824- print (f" Found component in BOM : { matchname } { matchver } " )
867+ print (f" Found component in bom import-events : { matchname } { matchver } " )
825868 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
826880
827881 # If we've gotten this far, the package is not in the BOM.
828882 # Now we need to figure out:
0 commit comments