Skip to content

Commit 08ef01d

Browse files
author
Shane Wright
committed
Improve accuracy of BOM component lookup
- Search the import-events - If that search fails, fall through to the standard component search - Track unique BOM matches by the name+ver string instead of URL, as import-events only gives us the matched name/ver data - Some minor typo fixes
1 parent 10fc4dc commit 08ef01d

File tree

1 file changed

+66
-12
lines changed

1 file changed

+66
-12
lines changed

examples/client/parse_spdx.py

Lines changed: 66 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,13 @@
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
5456
Requirements
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.
272275
def 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
482493
def 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+
644683
def 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

Comments
 (0)