Skip to content

Commit 9ecb60f

Browse files
author
Shane Wright
committed
- add a secondary scan status function (possibly redundant)
- add poll_for_sbom_scan (currently unused) - cleanup of comments - slight change in order of processing to ensure basic stuff like project/version are validated earlier - remove stray debugging code
1 parent 1fddbdd commit 9ecb60f

File tree

1 file changed

+105
-26
lines changed

1 file changed

+105
-26
lines changed

examples/client/parse_spdx.py

Lines changed: 105 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -156,18 +156,77 @@ def poll_for_upload(sbom_name):
156156
# Replace any spaces in the name with a dash to match BD
157157
sbom_name = sbom_name.replace(' ', '-')
158158

159-
# TODO also check for api/projects/<ver>/versions/<ver>/codelocations
160-
# -- status - operationNameCode = ServerScanning, operationName=Scanning, status
161-
# -- should be COMPLETED, not IN_PROGRESS
162-
# -- operatinName: Scanning
163159
# Search for the latest scan matching our SBOM
164-
# This might be a risk for a race condition
165160
params = {
166161
'q': [f"name:{sbom_name}"],
167162
'sort': ["updatedAt: ASC"]
168163
}
169-
170164
cls = bd.get_resource('codeLocations', params=params)
165+
for cl in cls:
166+
print(cl['name'])
167+
# Force exact match of: spdx_doc_name + " spdx/sbom"
168+
# BD appends the "spdx/sbom" string to the name.
169+
if cl['name'] != sbom_name + " spdx/sbom":
170+
continue
171+
172+
matched_scan = True
173+
for link in (cl['_meta']['links']):
174+
# Locate the scans URL to check for status
175+
if link['rel'] == "scans":
176+
summaries_url = link['href']
177+
break
178+
179+
assert(summaries_url)
180+
params = {
181+
'sort': ["updatedAt: ASC"]
182+
}
183+
184+
while (max_retries):
185+
max_retries -= 1
186+
for item in bd.get_items(summaries_url, params=params):
187+
# Only checking the first item as it's the most recent
188+
if item['scanState'] == "SUCCESS":
189+
print("BOM scan complete")
190+
return
191+
elif item['scanState'] == "FAILURE":
192+
logging.error(f"SPDX Scan Failure: {item['statusMessage']}")
193+
sys.exit(1)
194+
else:
195+
# Only other state should be "STARTED" -- keep polling
196+
print(f"Waiting for status success, currently: {item['scanState']}")
197+
time.sleep(sleep_time)
198+
# Break out of for loop so we always check the most recent
199+
break
200+
201+
# Handle various errors that might happen
202+
if max_retries == 0:
203+
logging.error("Failed to verify successful SPDX Scan in {max_retries * sleep_time} seconds")
204+
elif not matched_scan:
205+
logging.error(f"No scan found for SBOM: {sbom_name}")
206+
else:
207+
logging.error(f"Unable to verify successful scan of SBOM: {sbom_name}")
208+
# If we got this far, it's a fatal error.
209+
sys.exit(1)
210+
211+
# Poll for successful scan of SBOM
212+
# Inputs:
213+
# sbom_name: Name of SBOM document (not the filename)
214+
# version: project version to check
215+
# Returns on success. Errors will result in fatal exit.
216+
def poll_for_sbom_scan(sbom_name, projver):
217+
max_retries = 30
218+
sleep_time = 10
219+
matched_scan = False
220+
221+
# Replace any spaces in the name with a dash to match BD
222+
sbom_name = sbom_name.replace(' ', '-')
223+
224+
# Search for the latest scan matching our SBOM
225+
params = {
226+
'q': [f"name:{sbom_name}"],
227+
'sort': ["updatedAt: ASC"]
228+
}
229+
cls = bd.get_resource('codelocations', projver, params=params)
171230
for cl in cls:
172231
# Force exact match of: spdx_doc_name + " spdx/sbom"
173232
# BD appends the "spdx/sbom" string to the name.
@@ -213,7 +272,29 @@ def poll_for_upload(sbom_name):
213272
# If we got this far, it's a fatal error.
214273
sys.exit(1)
215274

216-
# TODO do we care about project_groups?
275+
# Poll for BOM completion
276+
# TODO currently unused, may delete
277+
# Input: Name of SBOM document (not the filename, the name defined inside the json body)
278+
# Returns on success. Errors will result in fatal exit.
279+
def poll_for_bom_complete(proj_version_url):
280+
max_retries = 30
281+
sleep_time = 10
282+
283+
while (max_retries):
284+
max_retries -= 1
285+
json_data = bd.get_json(proj_version_url + "/bom-status")
286+
if json_data['status'] == "UP_TO_DATE":
287+
return
288+
elif json_data['status'] == "FAILURE":
289+
logging.error(f"BOM Scan Failed")
290+
sys.exit(1)
291+
elif json_data['status'] == "NOT_INCLUDED":
292+
logging.error(f"BOM scan had no matches")
293+
sys.exit(1)
294+
else:
295+
print(f"Waiting for BOM scan success, currently: {json_data['status']}")
296+
time.sleep(sleep_time)
297+
217298
# Upload provided SBOM file to Black Duck
218299
# Inputs:
219300
# filename - Name of file to upload
@@ -460,21 +541,6 @@ def add_to_sbom(proj_version_url, comp_ver_url):
460541
global bd
461542
bd = Client(base_url=args.base_url, token=access_token, verify=args.verify)
462543

463-
upload_sbom_file(args.spdx_file, args.project_name, args.version_name)
464-
# This will exit if it fails
465-
poll_for_upload(document.creation_info.name)
466-
467-
# Open unmatched component file to save name, spdxid, version, and
468-
# origin/purl for later in json format
469-
# TODO this try/except isn't quite right
470-
try: outfile = open(args.out_file, 'w')
471-
except:
472-
logging.exception("Failed to open file for writing: " + args.out_file)
473-
sys.exit(1)
474-
475-
# Saved component data to write to file
476-
comps_out = []
477-
478544
# Fetch Project (can only have 1)
479545
params = {
480546
'q': [f"name:{args.project_name}"]
@@ -502,6 +568,20 @@ def add_to_sbom(proj_version_url, comp_ver_url):
502568

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

571+
upload_sbom_file(args.spdx_file, args.project_name, args.version_name)
572+
573+
# This will exit if it fails
574+
poll_for_upload(document.creation_info.name)
575+
# Also exits on failure. This may be somewhat redundant.
576+
poll_for_sbom_scan(document.creation_info.name, version)
577+
578+
# Open unmatched component file to save name, spdxid, version, and
579+
# origin/purl for later in json format
580+
try: outfile = open(args.out_file, 'w')
581+
except:
582+
logging.exception("Failed to open file for writing: " + args.out_file)
583+
sys.exit(1)
584+
505585
# Stats to track
506586
bom_matches = 0
507587
kb_matches = 0
@@ -512,6 +592,8 @@ def add_to_sbom(proj_version_url, comp_ver_url):
512592
cust_ver_count = 0
513593
# Saving all encountered components by their name+version (watching for repeats)
514594
packages = {}
595+
# Saved component data to write to file
596+
comps_out = []
515597

516598
# Walk through each component in the SPDX file
517599
for package in document.packages:
@@ -535,9 +617,8 @@ def add_to_sbom(proj_version_url, comp_ver_url):
535617
foundpurl = False
536618
for ref in package.external_references:
537619
# There can be multiple extrefs - try to locate a purl
620+
# If there should happen to be >1 purls, we only consider the first
538621
if (ref.reference_type == "purl"):
539-
# TODO are we guaranteed only 1 purl?
540-
# what would it mean to have >1?
541622
foundpurl = True
542623
kb_match = find_comp_in_kb(ref.locator)
543624
extref = ref.locator
@@ -583,8 +664,6 @@ def add_to_sbom(proj_version_url, comp_ver_url):
583664
if kb_match:
584665
print(f" WARNING: {matchname} {matchver} in KB but not in SBOM")
585666
add_to_sbom(proj_version_url, kb_match['version'])
586-
# temp debug to find this case
587-
quit()
588667
# short-circuit the rest
589668
continue
590669

0 commit comments

Comments
 (0)