Skip to content

Commit a84ed4e

Browse files
author
Shane Wright
committed
Debugging/cleanup/refactor. Add functionality to handle a situation where KB match was successful, but component was not located in BOM
1 parent 77046c8 commit a84ed4e

File tree

1 file changed

+99
-92
lines changed

1 file changed

+99
-92
lines changed

examples/client/parse_spdx.py

Lines changed: 99 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
Requirements
4040
4141
- python3 version 3.8 or newer recommended
42-
- the following packages are used by the script and should be installed
42+
- The following packages are used by the script and should be installed
4343
prior to use:
4444
argparse
4545
blackduck
@@ -60,9 +60,14 @@
6060
6161
pip3 install argparse blackduck sys logging time json spdx_tools
6262
63-
usage: parse_spdx.py [-h] --base-url BASE_URL --token-file TOKEN_FILE --spdx-file SPDX_FILE --out-file OUT_FILE --project PROJECT_NAME --version VERSION_NAME [--no-verify]
63+
usage: parse_spdx.py [-h] --base-url BASE_URL --token-file TOKEN_FILE
64+
--spdx-file SPDX_FILE --out-file OUT_FILE --project
65+
PROJECT_NAME --version VERSION_NAME
66+
[--license LICENSE_NAME] [--no-verify]
67+
[--no-spdx-validate]
6468
65-
Parse SPDX file and verify if component names are in current SBOM for given project-version
69+
Parse SPDX file and verify if component names are in current SBOM for given
70+
project-version
6671
6772
optional arguments:
6873
-h, --help show this help message and exit
@@ -76,7 +81,11 @@
7681
Project that contains the BOM components
7782
--version VERSION_NAME
7883
Version that contains the BOM components
84+
--license LICENSE_NAME
85+
License name to use for custom components (default:
86+
NOASSERTION)
7987
--no-verify Disable TLS certificate verification
88+
--no-spdx-validate Disable SPDX validation
8089
8190
'''
8291

@@ -145,6 +154,10 @@ def poll_for_upload(sbom_name):
145154
sleep_time = 10
146155
matched_scan = False
147156

157+
# TODO also check for api/projects/<ver>/versions/<ver>/codelocations
158+
# -- status - operationNameCode = ServerScanning, operationName=Scanning, status
159+
# -- should be COMPLETED, not IN_PROGRESS
160+
# -- operatinName: Scanning
148161
# Search for the latest scan matching our SBOM
149162
# This might be a risk for a race condition
150163
params = {
@@ -212,10 +225,10 @@ def upload_sbom_file(filename, project, version):
212225
files = {"file": (filename, open(filename,"rb"), mime_type)}
213226
fields = {"projectName": project, "versionName": version}
214227
response = bd.session.post("/api/scan/data", files = files, data=fields)
215-
logging.info(response)
228+
logging.debug(response)
216229

217230
if response.status_code == 409:
218-
logging.info(f"File {filename} is already mapped to a different project version")
231+
logging.error(f"File {filename} is already mapped to a different project version")
219232

220233
if response.status_code != 201:
221234
logging.error(f"Failed to upload SPDX file:")
@@ -227,41 +240,23 @@ def upload_sbom_file(filename, project, version):
227240

228241

229242
# Lookup the given pURL in the BD KB.
230-
# If successfully matched, update the associated package name and version with the data from the KB.
231-
# This will improve the accuracy of later lookups. We are replacing the SPDX input data with the
232-
# data stored in the KB.
233243
#
234244
# Inputs:
235-
# matchname - Name of package from the SPDX input file
236-
# matchver - Version of package
237-
# extref - pURL to look up
245+
# extref - pURL to look up
238246
#
239247
# Returns:
240-
# purlmatch - boolean (True if successful KB lookup)
241-
# matchname - Original parameter OR updated to reflect KB lookup name
242-
# matchver - Original parameter OR updated to reflect KB lookup version
243-
def find_comp_in_kb(matchname, matchver, extref):
244-
purlmatch = False
248+
# If match: API matching data (the "result" object)
249+
# No match: None
250+
def find_comp_in_kb(extref):
245251
params = {
246252
'packageUrl': extref
247253
}
248254
for result in bd.get_items("/api/search/purl-components", params=params):
249-
# This query should result in exactly 1 match
250-
purlmatch = True
251-
# Override the spdx name and use the known KB name
252-
if matchname != result['componentName']:
253-
print(f"Renaming {matchname} -> {result['componentName']}")
254-
matchname = result['componentName']
255-
# Override the spdx version and use the string from KB
256-
# for example, v2.8.5 -> 2.8.5
257-
if matchver != result['versionName']:
258-
print(f"Renaming {matchver} -> {result['versionName']}")
259-
matchver = result['versionName']
255+
# Should be exactly 1 match when successful
256+
return(result)
260257

261-
return(purlmatch, matchname, matchver)
262-
263-
# fall through -- lookup failed, so we keep the original name/ver
264-
return(purlmatch, matchname, matchver)
258+
# Fall through -- lookup failed
259+
return(None)
265260

266261

267262
# Locate component name + version in BOM
@@ -472,8 +467,13 @@ def add_to_sbom(proj_version_url, comp_ver_url):
472467
# This will exit if it fails
473468
poll_for_upload(document.creation_info.name)
474469

475-
# some little debug/test stubs
470+
# some debug/test stubs
476471
# TODO: delete these
472+
#ver="https://purl-validation.saas-staging.blackduck.com/api/projects/c2b4463f-7996-4c45-8443-b69b4f82ef1d/versions/67e4f6f5-2f42-42c4-9b69-e39bad55f907"
473+
#comp = "https://purl-validation.saas-staging.blackduck.com/api/components/fc0a76fe-70a4-4afa-9a94-c3c22d63454f/versions/fabaabb9-3b9a-4b5f-850a-39fe84c4cfc4"
474+
#add_to_sbom(ver, comp)
475+
#quit()
476+
477477
#matchcomp, matchver = find_cust_comp("ipaddress", "1.0.23")
478478
#if matchcomp:
479479
# print("matched comp")
@@ -493,12 +493,8 @@ def add_to_sbom(proj_version_url, comp_ver_url):
493493
#add_to_sbom(pv, cv)
494494
#quit()
495495

496-
# Open unmatched component file
497-
# Will save name, spdxid, version, and origin/purl for later in json format:
498-
# "name": "react-bootstrap",
499-
# "spdx_id": "SPDXRef-Pkg-react-bootstrap-2.1.2-30223",
500-
# "version": "2.1.2",
501-
# "origin": null
496+
# Open unmatched component file to save name, spdxid, version, and
497+
# origin/purl for later in json format
502498
# TODO this try/except isn't quite right
503499
try: outfile = open(args.out_file, 'w')
504500
except:
@@ -531,16 +527,6 @@ def add_to_sbom(proj_version_url, comp_ver_url):
531527

532528
logging.debug(f"Found {project['name']}:{version['versionName']}")
533529

534-
# situations to consider + actions
535-
# 1) No purl available : check SBOM for comp+ver, then add cust comp + add to SBOM
536-
# 2) Have purl + found in KB
537-
# - In SBOM? -> done
538-
# - Else -> add known KB comp to SBOM
539-
# *** this shouldn't happen in theory
540-
# 3) Have purl + not in KB (main case we are concerned with)
541-
# - In SBOM? (maybe already added or whatever?) -> done
542-
# - Else -> add cust comp + add to SBOM (same as 1)
543-
544530
# Stats to track
545531
bom_matches = 0
546532
kb_matches = 0
@@ -560,60 +546,81 @@ def add_to_sbom(proj_version_url, comp_ver_url):
560546
purlmatch = False
561547
matchname = package.name
562548
matchver = package.version
549+
print(f"Processing SPDX package: {matchname} {matchver}....")
563550
# Tracking unique package name + version from spdx file
564551
packages[matchname+matchver] = packages.get(matchname+matchver, 0) + 1
565552

566-
# NOTE: BD can change the original component name
567-
# EX: "React" -> "React from Facebook"
568553
if package.external_references:
569-
inkb, matchname, matchver = find_comp_in_kb(matchname, matchver, package.external_references[0].locator)
570-
if inkb: kb_matches += 1
554+
# TODO need to handle the possiblity of:
555+
# A) multiple extrefs
556+
# B) an extref that is not a purl
557+
# -- referenceType should be "purl" - ignore others?
558+
kb_match = find_comp_in_kb(package.external_references[0].locator)
559+
if (kb_match):
560+
# Update package name and version to reflect the KB name/ver
561+
print(f" KB match for {package.name} {package.version}")
562+
kb_matches+=1
563+
matchname = kb_match['componentName']
564+
matchver = kb_match['versionName']
565+
else:
566+
print(f" No KB match for {package.name} {package.version}")
571567
else:
572568
nopurl += 1
573-
print("No pURL found for component: ")
574-
print(" " + package.name)
575-
print(" " + package.spdx_id)
576-
print(" " + package.version)
569+
kb_match = None
570+
print(f"No pURL provided for {package.name} {package.version}")
577571

578572
if find_comp_in_bom(matchname, matchver, version):
579573
bom_matches += 1
580-
print(" Found comp match in BOM: " + matchname + matchver)
574+
print(f" Found component in BOM: {matchname} {matchver}")
575+
# It's in the BOM so we are happy
576+
# Everything else below is related to adding to the BOM
577+
continue
578+
579+
# If we've gotten this far, the package is not in the BOM.
580+
# Now we need to figure out:
581+
# - Is it already in the KB and we need to add it? (should be rare)
582+
# - Do we need to add a custom component?
583+
# - Do we need to add a version to an existing custom component?
584+
nomatch += 1
585+
print(f" Not present in BOM: {matchname} {matchver}")
586+
comp_data = {
587+
"name": package.name,
588+
"spdx_id": package.spdx_id,
589+
"version": package.version,
590+
"origin": extref
591+
}
592+
comps_out.append(comp_data)
593+
594+
# KB match was successful, but it wasn't in the BOM for some reason
595+
if kb_match:
596+
print(f" WARNING: {matchname} {matchver} in KB but not in SBOM")
597+
add_to_sbom(proj_version_url, kb_match['version'])
598+
# temp debug to find this case
599+
quit()
600+
# short-circuit the rest
601+
continue
602+
603+
# Check if custom component already exists
604+
comp_url, comp_ver_url = find_cust_comp(package.name, package.version)
605+
606+
if not comp_url:
607+
# Custom component did not exist, so create it
608+
cust_comp_count += 1
609+
comp_ver_url = create_cust_comp(package.name, package.version,
610+
args.license_name)
611+
elif comp_url and not comp_ver_url:
612+
# Custom component existed, but not the version we care about
613+
cust_ver_count += 1
614+
print(f" Adding version {package.version} to custom component {package.name}")
615+
comp_ver_url = create_cust_comp_ver(comp_url, package.version, args.license_name)
581616
else:
582-
nomatch += 1
583-
comp_data = {
584-
"name": package.name,
585-
"spdx_id": package.spdx_id,
586-
"version": package.version,
587-
"origin": extref
588-
}
589-
comps_out.append(comp_data)
590-
591-
# TODO what about: KB exists but not in BOM??
592-
# find_cust_comp is not generic enough for that situation
593-
#if inkb:
594-
# TODO handle add KB match to BOM here, short-circuit steps below
617+
print(" Custom component already exists, not in SBOM")
595618

596-
# Check if custom component already exists
597-
comp_url, comp_ver_url = find_cust_comp(package.name, package.version)
598-
599-
if not comp_url:
600-
# Custom component did not exist, so create it
601-
cust_comp_count += 1
602-
comp_ver_url = create_cust_comp(package.name, package.version,
603-
args.license_name)
604-
elif comp_url and not comp_ver_url:
605-
# Custom component existed, but not the version we care about
606-
cust_ver_count += 1
607-
print(f"Adding version {package.version} to custom component {package.name}")
608-
comp_ver_url = create_cust_comp_ver(comp_url, package.version, args.license_name)
609-
else:
610-
print("Custom component already exists, not in SBOM")
619+
# Shouldn't be possible
620+
assert(comp_ver_url), f"No component URL found for {package.name} {package.version}"
611621

612-
# is this possible?
613-
assert(comp_ver_url), f"No comp_ver URL found for {package.name} {package.version}"
614-
615-
print(f"Adding component to SBOM: {package.name} aka {matchname} {package.version}")
616-
add_to_sbom(proj_version_url, comp_ver_url)
622+
print(f" Adding component to SBOM: {package.name} aka {matchname} {package.version}")
623+
add_to_sbom(proj_version_url, comp_ver_url)
617624

618625
# Save unmatched components
619626
json.dump(comps_out, outfile)
@@ -622,9 +629,9 @@ def add_to_sbom(proj_version_url, comp_ver_url):
622629
print("\nStats: ")
623630
print("------")
624631
print(f" SPDX packages processed: {package_count}")
625-
print(f" Non matches: {nomatch}")
626-
print(f" KB matches: {kb_matches}")
632+
print(f" Packages missing from BOM: {nomatch}")
627633
print(f" BOM matches: {bom_matches}")
634+
print(f" KB matches: {kb_matches}")
628635
print(f" Packages missing purl: {nopurl}")
629636
print(f" Custom components created: {cust_comp_count}")
630637
print(f" Custom component versions created: {cust_ver_count}")

0 commit comments

Comments
 (0)