34
34
Version History
35
35
1.0 2023-09-26 Initial Release
36
36
1.1 2023-10-13 Updates to improve component matching of BD Component IDs
37
+ 1.2 2023-11-03 - Handle BD component with no version
38
+ - Fix bug related to extrefs with both purl and BD component data
39
+ - Check if project every had a "non-SBOM" scan and exit if so
40
+ - Fix some invalid sort parmaeter formatting
41
+ - Limit notification checking to last 24 hours
37
42
38
43
Requirements
39
44
50
55
spdx_tools
51
56
re
52
57
pathlib
58
+ datetime
53
59
54
60
- Blackduck instance
55
61
- API token with sufficient privileges
56
62
57
63
Install python packages with the following command:
58
64
59
- pip3 install argparse blackduck sys logging time json pprint pathlib spdx_tools
65
+ pip3 install datetime argparse blackduck sys logging time json pprint pathlib spdx_tools
60
66
61
67
usage: parse_spdx.py [-h] --base-url BASE_URL --token-file TOKEN_FILE
62
68
--spdx-file SPDX_FILE --out-file OUT_FILE --project
92
98
import logging
93
99
import time
94
100
import json
101
+ from datetime import datetime ,timedelta ,timezone
95
102
import re
96
103
from pprint import pprint
97
104
from pathlib import Path
103
110
# Used when we are polling for successful upload and processing
104
111
global MAX_RETRIES
105
112
global SLEEP
106
- MAX_RETRIES = 30
113
+ MAX_RETRIES = 60
107
114
SLEEP = 10
108
115
109
116
logging .basicConfig (
@@ -198,9 +205,15 @@ def poll_notifications_for_success(cl_url, proj_version_url, summaries_url):
198
205
retries = MAX_RETRIES
199
206
sleep_time = SLEEP
200
207
208
+ # Limit the query to the last 24 hours (very conservative but also
209
+ # keeps us from having to walk thousands of notifications every time)
210
+ today = datetime .now ().astimezone (timezone .utc )
211
+ yesterday = today - timedelta (days = 1 )
212
+ start = yesterday .strftime ("%Y-%m-%dT%H:%M:%S.000Z" )
201
213
params = {
202
214
'filter' : ["notificationType:VERSION_BOM_CODE_LOCATION_BOM_COMPUTED" ],
203
- 'sort' : ["createdAt: ASC" ]
215
+ 'sort' : ["createdAt DESC" ],
216
+ 'startDate' : [start ]
204
217
}
205
218
206
219
while (retries ):
@@ -211,18 +224,33 @@ def poll_notifications_for_success(cl_url, proj_version_url, summaries_url):
211
224
continue
212
225
# We're checking the entire list of notifications, but ours should
213
226
# be near the top.
214
- if result ['content' ]['projectVersion' ] == proj_version_url and \
215
- result ['content' ]['codeLocation' ] == cl_url and \
216
- result ['content' ]['scanSummary' ] == summaries_url :
217
- print ("BOM calculation complete" )
218
- return
227
+ if ( result ['content' ]['projectVersion' ] == proj_version_url and
228
+ result ['content' ]['codeLocation' ] == cl_url and
229
+ result ['content' ]['scanSummary' ] == summaries_url ) :
230
+ print ("BOM calculation complete" )
231
+ return
219
232
220
233
print ("Waiting for BOM calculation to complete" )
234
+ # For debugging
235
+ #print(f"Searching Notifications for:\n Proj_version: {proj_version_url}\n" +
236
+ # f" CodeLocation: {cl_url}\n Summaries: {summaries_url}")
221
237
time .sleep (sleep_time )
222
238
223
239
logging .error (f"Failed to verify successful BOM computed in { MAX_RETRIES * sleep_time } seconds" )
224
240
sys .exit (1 )
225
241
242
+ # Check if this project-version ever had a non-SBOM scan
243
+ # If so, we do not want to step on any toes and exit the script before
244
+ # attempting any SBOM import.
245
+ # Note: Uses an internal API for simplicity
246
+ def check_for_existing_scan (projver ):
247
+ headers = {'Accept' : 'application/vnd.blackducksoftware.internal-1+json' }
248
+ for source in bd .get_items (f"{ projver } /source-trees" , headers = headers ):
249
+ if not re .fullmatch (r".+spdx/sbom$" , source ['name' ]):
250
+ logging .error (f"Project has a non-SBOM scan. Details:" )
251
+ pprint (source )
252
+ sys .exit (1 )
253
+
226
254
# Poll for successful scan of SBOM.
227
255
# Inputs:
228
256
#
@@ -244,7 +272,7 @@ def poll_for_sbom_complete(sbom_name, proj_version_url):
244
272
# Search for the latest scan matching our SBOM name
245
273
params = {
246
274
'q' : [f"name:{ sbom_name } " ],
247
- 'sort' : ["updatedAt: ASC " ]
275
+ 'sort' : ["updatedAt DESC " ]
248
276
}
249
277
250
278
while (retries ):
@@ -411,6 +439,14 @@ def find_comp_id_in_kb(comp, ver):
411
439
# No component match
412
440
return None
413
441
kb_match ['componentName' ] = json_data ['name' ]
442
+ if ver is None :
443
+ # Special case where a component was provided but no version.
444
+ # Stick the component URL in the version field which we will later use
445
+ # to update the BOM. The name of this field is now overloaded but
446
+ # reusing it to stay generic.
447
+ kb_match ['version' ] = json_data ['_meta' ]['href' ]
448
+ kb_match ['versionName' ] = "UNKNOWN"
449
+ return kb_match
414
450
415
451
try :
416
452
json_data = bd .get_json (f"/api/components/{ comp } /versions/{ ver } " )
@@ -656,6 +692,7 @@ def import_sbom(bdobj, projname, vername, spdxfile, outfile=None, \
656
692
# Validate project/version details
657
693
project , version = get_proj_ver (projname , vername )
658
694
proj_version_url = version ['_meta' ]['href' ]
695
+ check_for_existing_scan (proj_version_url )
659
696
660
697
# Upload the provided SBOM
661
698
upload_sbom_file (spdxfile , projname , vername )
@@ -727,12 +764,15 @@ def import_sbom(bdobj, projname, vername, spdxfile, outfile=None, \
727
764
728
765
if "purl" in extrefs :
729
766
# purl is the preferred lookup
730
- kb_match = find_comp_in_kb (ref . locator )
731
- extref = ref . locator
767
+ kb_match = find_comp_in_kb (extrefs [ 'purl' ] )
768
+ extref = extrefs [ 'purl' ]
732
769
elif "BlackDuck-Component" in extrefs :
733
770
compid = normalize_id (extrefs ['BlackDuck-Component' ])
734
- verid = normalize_id (extrefs ['BlackDuck-ComponentVersion' ])
735
-
771
+ try :
772
+ verid = normalize_id (extrefs ['BlackDuck-ComponentVersion' ])
773
+ except :
774
+ print (" BD Component specified with no version" )
775
+ verid = None
736
776
# Lookup by KB ID
737
777
kb_match = find_comp_id_in_kb (compid , verid )
738
778
extref = extrefs ['BlackDuck-Component' ]
@@ -783,7 +823,7 @@ def import_sbom(bdobj, projname, vername, spdxfile, outfile=None, \
783
823
if kb_match :
784
824
kb_match_added_to_bom += 1
785
825
print (f" WARNING: { matchname } { matchver } found in KB but not in SBOM - adding it" )
786
- # kb_match['version'] contains the url of the component-version to add
826
+ # kb_match['version'] contains the component url to add
787
827
add_to_sbom (proj_version_url , kb_match ['version' ])
788
828
# short-circuit the rest
789
829
continue
0 commit comments