@@ -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):
460541global bd
461542bd = 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)
479545params = {
480546 'q' : [f"name:{ args .project_name } " ]
@@ -502,6 +568,20 @@ def add_to_sbom(proj_version_url, comp_ver_url):
502568
503569logging .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
506586bom_matches = 0
507587kb_matches = 0
@@ -512,6 +592,8 @@ def add_to_sbom(proj_version_url, comp_ver_url):
512592cust_ver_count = 0
513593# Saving all encountered components by their name+version (watching for repeats)
514594packages = {}
595+ # Saved component data to write to file
596+ comps_out = []
515597
516598# Walk through each component in the SPDX file
517599for 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