Skip to content

Commit d832762

Browse files
author
Shane Wright
committed
misc cleanup/bugfixes. make --license an optional arg that defaults to NOASSERTION
1 parent d13ae24 commit d832762

File tree

1 file changed

+83
-23
lines changed

1 file changed

+83
-23
lines changed

examples/client/parse_spdx.py

Lines changed: 83 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
pprint
5151
spdx_tools
5252
re
53+
pathlib
5354
5455
- Blackduck instance
5556
- API token with sufficient privileges to perform project version phase
@@ -87,13 +88,16 @@
8788
import json
8889
import re
8990
from pprint import pprint
91+
from pathlib import Path
9092
from spdx_tools.spdx.model.document import Document
9193
from spdx_tools.spdx.validation.document_validator import validate_full_spdx_document
9294
from spdx_tools.spdx.parser.error import SPDXParsingError
9395
from spdx_tools.spdx.parser.parse_anything import parse_file
9496

95-
96-
# Returns SPDX Document object on success, otherwise exits on parse failure
97+
# TODO what happens if file doesn't exist?
98+
# Returns SPDX Document object on success, otherwise exits on parse failure
99+
# Input: file = Filename to process
100+
# Returns: SPDX document object
97101
def spdx_parse(file):
98102
print("Parsing SPDX file...")
99103
start = time.process_time()
@@ -121,6 +125,7 @@ def spdx_validate(document):
121125
# sample data.
122126
logging.warning(validation_message.validation_message)
123127

128+
# TODO is it possible to make this a case-insensitive match?
124129
# Lookup the given matchname in the KB
125130
# Logs a successful match
126131
# Return the boolean purlmatch and matchname, which we might change from
@@ -144,6 +149,7 @@ def find_comp_in_kb(matchname, extref):
144149
return(purlmatch, result['componentName'])
145150
return(purlmatch, matchname)
146151

152+
# TODO is it possible to make this a case-insensitive match?
147153
# Locate component name + version in BOM
148154
# Returns True on success, False on failure
149155
def find_comp_in_bom(bd, compname, compver, projver):
@@ -173,6 +179,7 @@ def find_comp_in_bom(bd, compname, compver, projver):
173179
return False
174180

175181

182+
# TODO is it possible to make this a case-insensitive match?
176183
# Returns:
177184
# CompMatch - Contains matched component url, None for no match
178185
# VerMatch - Contains matched component verison url, None for no match
@@ -202,6 +209,7 @@ def find_cust_comp(cust_comp_name, cust_comp_version):
202209

203210
# Returns URL of matching license
204211
# Exits on failure, we assume it must pre-exist - TODO could probably just create this?
212+
# Note: License name search is case-sensitive
205213
def get_license_url(license_name):
206214
params = {
207215
'q': [f"name:{license_name}"]
@@ -214,8 +222,13 @@ def get_license_url(license_name):
214222
logging.error(f"Failed to find license {license_name}")
215223
sys.exit(1)
216224

225+
# Create a custom component
226+
# Inputs:
227+
# name - Name of component to add
228+
# version - Version of component to add
229+
# license - License name
217230
# Returns the URL for the newly created component version URL if successful
218-
def create_cust_comp(name, version, license, approval):
231+
def create_cust_comp(name, version, license):
219232
print(f"Adding custom component: {name} {version}")
220233
license_url = get_license_url(license)
221234
data = {
@@ -225,21 +238,34 @@ def create_cust_comp(name, version, license, approval):
225238
'license' : {
226239
'license' : license_url
227240
},
228-
},
229-
'approvalStatus': approval
241+
}
230242
}
231-
# TODO validate response
232-
# looks like a 412 if it already existed
233243
response = bd.session.post("api/components", json=data)
234-
# should be guaranteed 1 version because we just created it!
235-
# TODO put in a fail-safe
244+
logging.debug(response)
245+
if response.status_code == 412:
246+
# Shouldn't be possible. We checked for existence earlier.
247+
logging.error(f"Component {name} already exists")
248+
sys.exit(1)
249+
250+
if response.status_code != 201:
251+
# Shouldn't be possible. We checked for existence earlier.
252+
logging.error(response.json()['errors'][0]['errorMessage'])
253+
logging.error(f"Status code {response.status_code}")
254+
sys.exit(1)
255+
256+
# Should be guaranteed 1 version because we just created it!
236257
for version in bd.get_items(response.links['versions']['url']):
237258
return(version['_meta']['href'])
238259

239-
#return(response.links['self']['url'])
240260

241261
# Create a version for a custom component that already exists
242-
# Returns the component version url just created
262+
#
263+
# Inputs:
264+
# comp_url - API URL of the component to update
265+
# version - Version to add to existing component
266+
# license - License to use for version
267+
#
268+
# Returns: component version url just created
243269
def create_cust_comp_ver(comp_url, version, license):
244270
license_url = get_license_url(license)
245271
data = {
@@ -248,18 +274,33 @@ def create_cust_comp_ver(comp_url, version, license):
248274
'license' : license_url
249275
},
250276
}
251-
#response = bd.session.post(comp['_meta']['href'] + "/versions", json=data)
252277
response = bd.session.post(comp_url + "/versions", json=data)
253-
# TODO validate response
254-
return(response.links['versions']['url'])
278+
logging.debug(response)
279+
if response.status_code == 412:
280+
# Shouldn't be possible. We checked for existence earlier.
281+
logging.error(f"Version {version} already exists for component")
282+
sys.exit(1)
283+
284+
# necessary?
285+
if response.status_code != 201:
286+
logging.error(f"Failed to add Version {version} to component")
287+
sys.exit(1)
288+
289+
return(response.links['self']['url'])
255290

256291
# Add specified component version url to our project+version SBOM
292+
# Inputs:
293+
# proj_version_url: API URL for a project+version to update
294+
# comp_ver_url: API URL of a component+version to add
257295
def add_to_sbom(proj_version_url, comp_ver_url):
258296
data = {
259297
'component': comp_ver_url
260298
}
261-
# TODO validate response
262299
response = bd.session.post(proj_version_url + "/components", json=data)
300+
if (response.status_code != 200):
301+
logging.error(response.json()['errors'][0]['errorMessage'])
302+
logging.error(f"Status code {response.status_code}")
303+
sys.exit(1)
263304

264305
parser = argparse.ArgumentParser(description="Parse SPDX file and verify if component names are in current SBOM for given project-version")
265306
parser.add_argument("--base-url", required=True, help="Hub server URL e.g. https://your.blackduck.url")
@@ -268,6 +309,7 @@ def add_to_sbom(proj_version_url, comp_ver_url):
268309
parser.add_argument("--out-file", dest='out_file', required=True, help="Unmatched components file")
269310
parser.add_argument("--project", dest='project_name', required=True, help="Project that contains the BOM components")
270311
parser.add_argument("--version", dest='version_name', required=True, help="Version that contains the BOM components")
312+
parser.add_argument("--license", dest='license_name', required=False, default="NOASSERTION", help="License name to use for custom components")
271313
parser.add_argument("--no-verify", dest='verify', action='store_false', help="Disable TLS certificate verification")
272314
parser.add_argument("--no-spdx-validate", dest='spdx_validate', action='store_false', help="Disable SPDX validation")
273315
args = parser.parse_args()
@@ -277,21 +319,38 @@ def add_to_sbom(proj_version_url, comp_ver_url):
277319
format="[%(asctime)s] {%(module)s:%(lineno)d} %(levelname)s - %(message)s"
278320
)
279321

280-
document = spdx_parse(args.spdx_file)
281-
if (args.spdx_validate):
282-
spdx_validate(document)
322+
if (Path(args.spdx_file).is_file()):
323+
document = spdx_parse(args.spdx_file)
324+
if (args.spdx_validate):
325+
spdx_validate(document)
326+
else:
327+
logging.error(f"Invalid SPDX file: {args.spdx_file}")
328+
sys.exit(1)
283329

284330
with open(args.token_file, 'r') as tf:
285331
access_token = tf.readline().strip()
286332

287333
bd = Client(base_url=args.base_url, token=access_token, verify=args.verify)
288334

335+
# some little debug/test stubs
336+
# TODO: delete these
337+
#comp_ver_url = create_cust_comp("MY COMPONENT z", "1", args.license_name)
338+
#
339+
#comp_url = "https://purl-validation.saas-staging.blackduck.com/api/components/886c04d4-28ce-4a27-be4c-f083e73a9f69"
340+
#comp_ver_url = create_cust_comp_ver(comp_url, "701", "NOASSERTION")
341+
#
342+
#pv = "https://purl-validation.saas-staging.blackduck.com/api/projects/14b714d0-fa37-4684-86cc-ed4e7cc64b89/versions/b8426ca3-1e27-4045-843b-003eca72f98e"
343+
#cv = "https://purl-validation.saas-staging.blackduck.com/api/components/886c04d4-28ce-4a27-be4c-f083e73a9f69/versions/56f64b7f-c284-457d-b593-0cf19a272a19"
344+
#add_to_sbom(pv, cv)
345+
#quit()
346+
289347
# Open unmatched component file
290348
# Will save name, spdxid, version, and origin/purl for later in json format:
291349
# "name": "react-bootstrap",
292350
# "spdx_id": "SPDXRef-Pkg-react-bootstrap-2.1.2-30223",
293351
# "version": "2.1.2",
294352
# "origin": null
353+
# TODO this try/except actually isn't right
295354
try: outfile = open(args.out_file, 'w')
296355
except:
297356
logging.exception("Failed to open file for writing: " + args.out_file)
@@ -383,17 +442,18 @@ def add_to_sbom(proj_version_url, comp_ver_url):
383442
# Check if custom component already exists
384443
comp_url, comp_ver_url = find_cust_comp(package.name, package.version)
385444

386-
# TODO make these optional args with defaults
387-
license = "NOASSERTION"
388-
approval = "UNREVIEWED"
389445
if not comp_url:
446+
# Custom component did not exist, so create it
390447
cust_comp_count += 1
391448
comp_ver_url = create_cust_comp(package.name, package.version,
392-
license, approval)
449+
args.license_name, approval)
393450
elif comp_url and not comp_ver_url:
451+
# Custom component existed, but not the version we care about
394452
cust_ver_count += 1
395453
print(f"Adding version {package.version} to custom component {package.name}")
396-
comp_ver_url = create_cust_comp_ver(comp_url, package.version, license)
454+
comp_ver_url = create_cust_comp_ver(comp_url, package.version, args.license_name)
455+
# DEBUG
456+
quit()
397457
else:
398458
print("Custom component already exists, not in SBOM")
399459

0 commit comments

Comments
 (0)