Skip to content

Commit 5a4b105

Browse files
author
swright-synopsys
committed
- Handle BD component with no version
- Fix bug related to extrefs with both purl and BD component data - Check if project every had a "non-SBOM" scan and exit if so - Fix some invalid sort parmaeter formatting - Limit notification checking to last 24 hours
1 parent 68291b1 commit 5a4b105

File tree

1 file changed

+54
-14
lines changed

1 file changed

+54
-14
lines changed

examples/client/parse_spdx.py

Lines changed: 54 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@
3434
Version History
3535
1.0 2023-09-26 Initial Release
3636
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
3742
3843
Requirements
3944
@@ -50,13 +55,14 @@
5055
spdx_tools
5156
re
5257
pathlib
58+
datetime
5359
5460
- Blackduck instance
5561
- API token with sufficient privileges
5662
5763
Install python packages with the following command:
5864
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
6066
6167
usage: parse_spdx.py [-h] --base-url BASE_URL --token-file TOKEN_FILE
6268
--spdx-file SPDX_FILE --out-file OUT_FILE --project
@@ -92,6 +98,7 @@
9298
import logging
9399
import time
94100
import json
101+
from datetime import datetime,timedelta,timezone
95102
import re
96103
from pprint import pprint
97104
from pathlib import Path
@@ -103,7 +110,7 @@
103110
# Used when we are polling for successful upload and processing
104111
global MAX_RETRIES
105112
global SLEEP
106-
MAX_RETRIES = 30
113+
MAX_RETRIES = 60
107114
SLEEP = 10
108115

109116
logging.basicConfig(
@@ -198,9 +205,15 @@ def poll_notifications_for_success(cl_url, proj_version_url, summaries_url):
198205
retries = MAX_RETRIES
199206
sleep_time = SLEEP
200207

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")
201213
params = {
202214
'filter': ["notificationType:VERSION_BOM_CODE_LOCATION_BOM_COMPUTED"],
203-
'sort' : ["createdAt: ASC"]
215+
'sort' : ["createdAt DESC"],
216+
'startDate' : [start]
204217
}
205218

206219
while (retries):
@@ -211,18 +224,33 @@ def poll_notifications_for_success(cl_url, proj_version_url, summaries_url):
211224
continue
212225
# We're checking the entire list of notifications, but ours should
213226
# 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
219232

220233
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}")
221237
time.sleep(sleep_time)
222238

223239
logging.error(f"Failed to verify successful BOM computed in {MAX_RETRIES * sleep_time} seconds")
224240
sys.exit(1)
225241

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+
226254
# Poll for successful scan of SBOM.
227255
# Inputs:
228256
#
@@ -244,7 +272,7 @@ def poll_for_sbom_complete(sbom_name, proj_version_url):
244272
# Search for the latest scan matching our SBOM name
245273
params = {
246274
'q': [f"name:{sbom_name}"],
247-
'sort': ["updatedAt: ASC"]
275+
'sort': ["updatedAt DESC"]
248276
}
249277

250278
while (retries):
@@ -411,6 +439,14 @@ def find_comp_id_in_kb(comp, ver):
411439
# No component match
412440
return None
413441
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
414450

415451
try:
416452
json_data = bd.get_json(f"/api/components/{comp}/versions/{ver}")
@@ -656,6 +692,7 @@ def import_sbom(bdobj, projname, vername, spdxfile, outfile=None, \
656692
# Validate project/version details
657693
project, version = get_proj_ver(projname, vername)
658694
proj_version_url = version['_meta']['href']
695+
check_for_existing_scan(proj_version_url)
659696

660697
# Upload the provided SBOM
661698
upload_sbom_file(spdxfile, projname, vername)
@@ -727,12 +764,15 @@ def import_sbom(bdobj, projname, vername, spdxfile, outfile=None, \
727764

728765
if "purl" in extrefs:
729766
# 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']
732769
elif "BlackDuck-Component" in extrefs:
733770
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
736776
# Lookup by KB ID
737777
kb_match = find_comp_id_in_kb(compid, verid)
738778
extref = extrefs['BlackDuck-Component']
@@ -783,7 +823,7 @@ def import_sbom(bdobj, projname, vername, spdxfile, outfile=None, \
783823
if kb_match:
784824
kb_match_added_to_bom += 1
785825
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
787827
add_to_sbom(proj_version_url, kb_match['version'])
788828
# short-circuit the rest
789829
continue

0 commit comments

Comments
 (0)