@@ -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.
129129def 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
176179def 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?
203205def 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
215218def 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
249264parser = argparse .ArgumentParser (description = "Parse SPDX file and verify if component names are in current SBOM for given project-version" )
250265parser .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):
294309assert len (projects ) == 1 , \
295310 f"There should one project named { args .project_name } . Found { len (projects )} "
296311project = projects [0 ]
297-
298312# Fetch Version (can only have 1)
299313params = {
300314 'q' : [f"versionName:{ args .version_name } " ]
@@ -304,6 +318,7 @@ def create_cust_comp_ver(comp, version, license):
304318assert len (versions ) == 1 , \
305319 f"There should be 1 version named { args .version_name } . Found { len (versions )} "
306320version = versions [0 ]
321+ proj_version_url = version ['_meta' ]['href' ]
307322
308323logging .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
390406json .dump (comps_out , outfile )
391407outfile .close ()
@@ -399,6 +415,5 @@ def create_cust_comp_ver(comp, version, license):
399415print (f" Packages missing purl: { nopurl } " )
400416print (f" Custom components created: { cust_comp_count } " )
401417print (f" Custom component versions created: { cust_ver_count } " )
402-
403418#pprint(packages)
404419print (f" { len (packages )} unique packages processed" )
0 commit comments