@@ -124,7 +124,7 @@ def spdx_validate(document):
124
124
# Lookup the given matchname in the KB
125
125
# Logs a successful match
126
126
# 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
128
128
# That way we can more accurately search the BOM later.
129
129
def find_comp_in_kb (matchname , extref ):
130
130
# KB lookup to check for pURL match
@@ -135,13 +135,12 @@ def find_comp_in_kb(matchname, extref):
135
135
# TODO any other action to take here?
136
136
# We should probably track KB matches?
137
137
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?
140
139
purlmatch = True
141
140
# in this event, override the spdx name and use the known KB name
142
- # ( is version mangling possible??)
141
+ # TODO: is version mangling possible?
143
142
if matchname != result ['componentName' ]:
144
- print (f"updating { matchname } -> { result ['componentName' ]} " )
143
+ print (f"Renaming { matchname } -> { result ['componentName' ]} " )
145
144
return (purlmatch , result ['componentName' ])
146
145
return (purlmatch , matchname )
147
146
@@ -151,7 +150,7 @@ def find_comp_in_bom(bd, compname, compver, projver):
151
150
have_match = False
152
151
num_match = 0
153
152
154
- # Lookup existing SBOM for a match (just on name to start)
153
+ # Lookup existing SBOM for a match
155
154
# This is a fuzzy match (see "react" for an example)
156
155
params = {
157
156
'q' : [f"componentOrVersionName:{ compname } " ]
@@ -162,44 +161,47 @@ def find_comp_in_bom(bd, compname, compver, projver):
162
161
for comp in comps :
163
162
if comp ['componentName' ] != compname :
164
163
# The BD API search is inexact. Force our match to be precise.
165
- #print(f"fuzzy match failed us: {comp['componentName']} vs {compname}")
166
164
continue
167
165
# 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
170
173
return False
171
174
172
175
173
176
# 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
176
179
def find_cust_comp (cust_comp_name , cust_comp_version ):
177
180
params = {
178
181
'q' : [f"name:{ cust_comp_name } " ]
179
182
}
180
183
181
184
matched_comp = None
185
+ matched_ver = None
182
186
# Relies on internal header
183
187
headers = {'Accept' : 'application/vnd.blackducksoftware.internal-1+json' }
184
- ver_match = False
185
188
for comp in bd .get_resource ('components' , params = params , headers = headers ):
186
- print (f"{ comp ['name' ]} " )
187
189
if cust_comp_name != comp ['name' ]:
188
190
# Skip it. We want to be precise in our matching, despite the API.
189
191
continue
190
- matched_comp = comp
192
+ matched_comp = comp [ '_meta' ][ 'href' ]
191
193
# Check version
192
194
for version in bd .get_resource ('versions' , comp ):
193
195
if cust_comp_version == version ['versionName' ]:
194
196
# 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 )
197
199
198
- return (matched_comp , ver_match )
200
+ return (matched_comp , matched_ver )
199
201
200
202
201
203
# 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?
203
205
def get_license_url (license_name ):
204
206
params = {
205
207
'q' : [f"name:{ license_name } " ]
@@ -212,6 +214,7 @@ def get_license_url(license_name):
212
214
logging .error (f"Failed to find license { license_name } " )
213
215
sys .exit (1 )
214
216
217
+ # Returns the URL for the newly created component version URL if successful
215
218
def create_cust_comp (name , version , license , approval ):
216
219
print (f"Adding custom component: { name } { version } " )
217
220
license_url = get_license_url (license )
@@ -225,26 +228,38 @@ def create_cust_comp(name, version, license, approval):
225
228
},
226
229
'approvalStatus' : approval
227
230
}
228
- response = bd .session .post ("api/components" , json = data )
229
- pprint (response )
230
-
231
231
# TODO validate response
232
232
# 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'])
233
240
234
241
# 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 ):
238
244
license_url = get_license_url (license )
239
245
data = {
240
246
'versionName' : version ,
241
247
'license' : {
242
248
'license' : license_url
243
249
},
244
250
}
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 )
247
253
# 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 )
248
263
249
264
parser = argparse .ArgumentParser (description = "Parse SPDX file and verify if component names are in current SBOM for given project-version" )
250
265
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):
294
309
assert len (projects ) == 1 , \
295
310
f"There should one project named { args .project_name } . Found { len (projects )} "
296
311
project = projects [0 ]
297
-
298
312
# Fetch Version (can only have 1)
299
313
params = {
300
314
'q' : [f"versionName:{ args .version_name } " ]
@@ -304,6 +318,7 @@ def create_cust_comp_ver(comp, version, license):
304
318
assert len (versions ) == 1 , \
305
319
f"There should be 1 version named { args .version_name } . Found { len (versions )} "
306
320
version = versions [0 ]
321
+ proj_version_url = version ['_meta' ]['href' ]
307
322
308
323
logging .debug (f"Found { project ['name' ]} :{ version ['versionName' ]} " )
309
324
@@ -366,26 +381,27 @@ def create_cust_comp_ver(comp, version, license):
366
381
comps_out .append (comp_data )
367
382
368
383
# 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 )
370
385
371
386
# TODO make these optional args with defaults
372
387
license = "NOASSERTION"
373
388
approval = "UNREVIEWED"
374
- if not comp_match :
389
+ if not comp_url :
375
390
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 :
378
394
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 )
381
397
else :
382
- # nothing to do?
383
- print ("probably found name and ver" )
398
+ print ("Custom component already exists, not in SBOM" )
384
399
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 )
387
404
388
-
389
405
# Save unmatched components
390
406
json .dump (comps_out , outfile )
391
407
outfile .close ()
@@ -399,6 +415,5 @@ def create_cust_comp_ver(comp, version, license):
399
415
print (f" Packages missing purl: { nopurl } " )
400
416
print (f" Custom components created: { cust_comp_count } " )
401
417
print (f" Custom component versions created: { cust_ver_count } " )
402
-
403
418
#pprint(packages)
404
419
print (f" { len (packages )} unique packages processed" )
0 commit comments