@@ -156,18 +156,77 @@ def poll_for_upload(sbom_name):
156
156
# Replace any spaces in the name with a dash to match BD
157
157
sbom_name = sbom_name .replace (' ' , '-' )
158
158
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
163
159
# Search for the latest scan matching our SBOM
164
- # This might be a risk for a race condition
165
160
params = {
166
161
'q' : [f"name:{ sbom_name } " ],
167
162
'sort' : ["updatedAt: ASC" ]
168
163
}
169
-
170
164
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 )
171
230
for cl in cls :
172
231
# Force exact match of: spdx_doc_name + " spdx/sbom"
173
232
# BD appends the "spdx/sbom" string to the name.
@@ -213,7 +272,29 @@ def poll_for_upload(sbom_name):
213
272
# If we got this far, it's a fatal error.
214
273
sys .exit (1 )
215
274
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
+
217
298
# Upload provided SBOM file to Black Duck
218
299
# Inputs:
219
300
# filename - Name of file to upload
@@ -460,21 +541,6 @@ def add_to_sbom(proj_version_url, comp_ver_url):
460
541
global bd
461
542
bd = Client (base_url = args .base_url , token = access_token , verify = args .verify )
462
543
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
-
478
544
# Fetch Project (can only have 1)
479
545
params = {
480
546
'q' : [f"name:{ args .project_name } " ]
@@ -502,6 +568,20 @@ def add_to_sbom(proj_version_url, comp_ver_url):
502
568
503
569
logging .debug (f"Found { project ['name' ]} :{ version ['versionName' ]} " )
504
570
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
+
505
585
# Stats to track
506
586
bom_matches = 0
507
587
kb_matches = 0
@@ -512,6 +592,8 @@ def add_to_sbom(proj_version_url, comp_ver_url):
512
592
cust_ver_count = 0
513
593
# Saving all encountered components by their name+version (watching for repeats)
514
594
packages = {}
595
+ # Saved component data to write to file
596
+ comps_out = []
515
597
516
598
# Walk through each component in the SPDX file
517
599
for package in document .packages :
@@ -535,9 +617,8 @@ def add_to_sbom(proj_version_url, comp_ver_url):
535
617
foundpurl = False
536
618
for ref in package .external_references :
537
619
# There can be multiple extrefs - try to locate a purl
620
+ # If there should happen to be >1 purls, we only consider the first
538
621
if (ref .reference_type == "purl" ):
539
- # TODO are we guaranteed only 1 purl?
540
- # what would it mean to have >1?
541
622
foundpurl = True
542
623
kb_match = find_comp_in_kb (ref .locator )
543
624
extref = ref .locator
@@ -583,8 +664,6 @@ def add_to_sbom(proj_version_url, comp_ver_url):
583
664
if kb_match :
584
665
print (f" WARNING: { matchname } { matchver } in KB but not in SBOM" )
585
666
add_to_sbom (proj_version_url , kb_match ['version' ])
586
- # temp debug to find this case
587
- quit ()
588
667
# short-circuit the rest
589
668
continue
590
669
0 commit comments