Skip to content

Commit d13ae24

Browse files
author
Shane Wright
committed
tons of cleanup/bugfixes + add_to_sbom utility
1 parent 9507969 commit d13ae24

File tree

1 file changed

+54
-39
lines changed

1 file changed

+54
-39
lines changed

examples/client/parse_spdx.py

Lines changed: 54 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ def spdx_validate(document):
124124
# Lookup the given matchname in the KB
125125
# Logs a successful match
126126
# Return the boolean purlmatch and matchname, which we might change from
127-
# its original value -- we will force it to be the same as the name in BD.
127+
# its original value -- we will force it to be the same as the name in the KB
128128
# That way we can more accurately search the BOM later.
129129
def find_comp_in_kb(matchname, extref):
130130
# KB lookup to check for pURL match
@@ -135,13 +135,12 @@ def find_comp_in_kb(matchname, extref):
135135
# TODO any other action to take here?
136136
# We should probably track KB matches?
137137
for result in bd.get_items("/api/search/purl-components", params=params):
138-
# do we need to worry about more than 1 match?
139-
#print(f"Found KB match for {extref}")
138+
# TODO do we need to worry about more than 1 match?
140139
purlmatch = True
141140
# in this event, override the spdx name and use the known KB name
142-
# (is version mangling possible??)
141+
# TODO: is version mangling possible?
143142
if matchname != result['componentName']:
144-
print(f"updating {matchname} -> {result['componentName']}")
143+
print(f"Renaming {matchname} -> {result['componentName']}")
145144
return(purlmatch, result['componentName'])
146145
return(purlmatch, matchname)
147146

@@ -151,7 +150,7 @@ def find_comp_in_bom(bd, compname, compver, projver):
151150
have_match = False
152151
num_match = 0
153152

154-
# Lookup existing SBOM for a match (just on name to start)
153+
# Lookup existing SBOM for a match
155154
# This is a fuzzy match (see "react" for an example)
156155
params = {
157156
'q': [f"componentOrVersionName:{compname}"]
@@ -162,44 +161,47 @@ def find_comp_in_bom(bd, compname, compver, projver):
162161
for comp in comps:
163162
if comp['componentName'] != compname:
164163
# The BD API search is inexact. Force our match to be precise.
165-
#print(f"fuzzy match failed us: {comp['componentName']} vs {compname}")
166164
continue
167165
# Check component name + version name
168-
if comp['componentVersionName'] == compver:
169-
return True
166+
try:
167+
if comp['componentVersionName'] == compver:
168+
return True
169+
except:
170+
# Handle situation where it's missing the version name for some reason
171+
print(f"comp {compname} in BOM has no version!")
172+
return False
170173
return False
171174

172175

173176
# Returns:
174-
# CompMatch - Contains matched component object, None for no match
175-
# FoundVer - Boolen: True if matched the custom component version
177+
# CompMatch - Contains matched component url, None for no match
178+
# VerMatch - Contains matched component verison url, None for no match
176179
def find_cust_comp(cust_comp_name, cust_comp_version):
177180
params = {
178181
'q': [f"name:{cust_comp_name}"]
179182
}
180183

181184
matched_comp = None
185+
matched_ver = None
182186
# Relies on internal header
183187
headers = {'Accept': 'application/vnd.blackducksoftware.internal-1+json'}
184-
ver_match = False
185188
for comp in bd.get_resource('components', params=params, headers=headers):
186-
print(f"{comp['name']}")
187189
if cust_comp_name != comp['name']:
188190
# Skip it. We want to be precise in our matching, despite the API.
189191
continue
190-
matched_comp = comp
192+
matched_comp = comp['_meta']['href']
191193
# Check version
192194
for version in bd.get_resource('versions', comp):
193195
if cust_comp_version == version['versionName']:
194196
# Successfully matched both name and version
195-
ver_match = True
196-
return(matched_comp, ver_match)
197+
matched_ver = version['_meta']['href']
198+
return(matched_comp, matched_ver)
197199

198-
return(matched_comp, ver_match)
200+
return(matched_comp, matched_ver)
199201

200202

201203
# Returns URL of matching license
202-
# Exits on failure, we assume it must exist - TODO could probably just create this?
204+
# Exits on failure, we assume it must pre-exist - TODO could probably just create this?
203205
def get_license_url(license_name):
204206
params = {
205207
'q': [f"name:{license_name}"]
@@ -212,6 +214,7 @@ def get_license_url(license_name):
212214
logging.error(f"Failed to find license {license_name}")
213215
sys.exit(1)
214216

217+
# Returns the URL for the newly created component version URL if successful
215218
def create_cust_comp(name, version, license, approval):
216219
print(f"Adding custom component: {name} {version}")
217220
license_url = get_license_url(license)
@@ -225,26 +228,38 @@ def create_cust_comp(name, version, license, approval):
225228
},
226229
'approvalStatus': approval
227230
}
228-
response = bd.session.post("api/components", json=data)
229-
pprint(response)
230-
231231
# TODO validate response
232232
# looks like a 412 if it already existed
233+
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
236+
for version in bd.get_items(response.links['versions']['url']):
237+
return(version['_meta']['href'])
238+
239+
#return(response.links['self']['url'])
233240

234241
# Create a version for a custom component that already exists
235-
# The comp argument is the component object from previous lookup
236-
def create_cust_comp_ver(comp, version, license):
237-
print(f"Adding version {version} to custom component {comp['name']}")
242+
# Returns the component version url just created
243+
def create_cust_comp_ver(comp_url, version, license):
238244
license_url = get_license_url(license)
239245
data = {
240246
'versionName' : version,
241247
'license' : {
242248
'license' : license_url
243249
},
244250
}
245-
response = bd.session.post(comp['_meta']['href'] + "/versions", json=data)
246-
pprint(response)
251+
#response = bd.session.post(comp['_meta']['href'] + "/versions", json=data)
252+
response = bd.session.post(comp_url + "/versions", json=data)
247253
# TODO validate response
254+
return(response.links['versions']['url'])
255+
256+
# Add specified component version url to our project+version SBOM
257+
def add_to_sbom(proj_version_url, comp_ver_url):
258+
data = {
259+
'component': comp_ver_url
260+
}
261+
# TODO validate response
262+
response = bd.session.post(proj_version_url + "/components", json=data)
248263

249264
parser = argparse.ArgumentParser(description="Parse SPDX file and verify if component names are in current SBOM for given project-version")
250265
parser.add_argument("--base-url", required=True, help="Hub server URL e.g. https://your.blackduck.url")
@@ -294,7 +309,6 @@ def create_cust_comp_ver(comp, version, license):
294309
assert len(projects) == 1, \
295310
f"There should one project named {args.project_name}. Found {len(projects)}"
296311
project = projects[0]
297-
298312
# Fetch Version (can only have 1)
299313
params = {
300314
'q': [f"versionName:{args.version_name}"]
@@ -304,6 +318,7 @@ def create_cust_comp_ver(comp, version, license):
304318
assert len(versions) == 1, \
305319
f"There should be 1 version named {args.version_name}. Found {len(versions)}"
306320
version = versions[0]
321+
proj_version_url = version['_meta']['href']
307322

308323
logging.debug(f"Found {project['name']}:{version['versionName']}")
309324

@@ -366,26 +381,27 @@ def create_cust_comp_ver(comp, version, license):
366381
comps_out.append(comp_data)
367382

368383
# Check if custom component already exists
369-
comp_match, found_ver = find_cust_comp(package.name, package.version)
384+
comp_url, comp_ver_url = find_cust_comp(package.name, package.version)
370385

371386
# TODO make these optional args with defaults
372387
license = "NOASSERTION"
373388
approval = "UNREVIEWED"
374-
if not comp_match:
389+
if not comp_url:
375390
cust_comp_count += 1
376-
create_cust_comp(package.name, package.version, license, approval)
377-
elif comp_match and not found_ver:
391+
comp_ver_url = create_cust_comp(package.name, package.version,
392+
license, approval)
393+
elif comp_url and not comp_ver_url:
378394
cust_ver_count += 1
379-
print("Adding custom component version...")
380-
create_cust_comp_ver(comp_match, package.version, license)
395+
print(f"Adding version {package.version} to custom component {package.name}")
396+
comp_ver_url = create_cust_comp_ver(comp_url, package.version, license)
381397
else:
382-
# nothing to do?
383-
print("probably found name and ver")
398+
print("Custom component already exists, not in SBOM")
384399

385-
# TODO write sbom add code
386-
#add_to_sbom()
400+
# is this possible? i don't think so
401+
assert(comp_ver_url), f"No comp_ver URL found for {package.name} {package.version}"
402+
print(f"Adding component to SBOM: {package.name} {package.version}")
403+
add_to_sbom(proj_version_url, comp_ver_url)
387404

388-
389405
# Save unmatched components
390406
json.dump(comps_out, outfile)
391407
outfile.close()
@@ -399,6 +415,5 @@ def create_cust_comp_ver(comp, version, license):
399415
print(f" Packages missing purl: {nopurl}")
400416
print(f" Custom components created: {cust_comp_count}")
401417
print(f" Custom component versions created: {cust_ver_count}")
402-
403418
#pprint(packages)
404419
print(f" {len(packages)} unique packages processed")

0 commit comments

Comments
 (0)