3939Requirements
4040
4141- python3 version 3.8 or newer recommended
42- - the following packages are used by the script and should be installed
42+ - The following packages are used by the script and should be installed
4343 prior to use:
4444 argparse
4545 blackduck
6060
6161 pip3 install argparse blackduck sys logging time json spdx_tools
6262
63- usage: parse_spdx.py [-h] --base-url BASE_URL --token-file TOKEN_FILE --spdx-file SPDX_FILE --out-file OUT_FILE --project PROJECT_NAME --version VERSION_NAME [--no-verify]
63+ usage: parse_spdx.py [-h] --base-url BASE_URL --token-file TOKEN_FILE
64+ --spdx-file SPDX_FILE --out-file OUT_FILE --project
65+ PROJECT_NAME --version VERSION_NAME
66+ [--license LICENSE_NAME] [--no-verify]
67+ [--no-spdx-validate]
6468
65- Parse SPDX file and verify if component names are in current SBOM for given project-version
69+ Parse SPDX file and verify if component names are in current SBOM for given
70+ project-version
6671
6772optional arguments:
6873 -h, --help show this help message and exit
7681 Project that contains the BOM components
7782 --version VERSION_NAME
7883 Version that contains the BOM components
84+ --license LICENSE_NAME
85+ License name to use for custom components (default:
86+ NOASSERTION)
7987 --no-verify Disable TLS certificate verification
88+ --no-spdx-validate Disable SPDX validation
8089
8190'''
8291
@@ -145,6 +154,10 @@ def poll_for_upload(sbom_name):
145154 sleep_time = 10
146155 matched_scan = False
147156
157+ # TODO also check for api/projects/<ver>/versions/<ver>/codelocations
158+ # -- status - operationNameCode = ServerScanning, operationName=Scanning, status
159+ # -- should be COMPLETED, not IN_PROGRESS
160+ # -- operatinName: Scanning
148161 # Search for the latest scan matching our SBOM
149162 # This might be a risk for a race condition
150163 params = {
@@ -212,10 +225,10 @@ def upload_sbom_file(filename, project, version):
212225 files = {"file" : (filename , open (filename ,"rb" ), mime_type )}
213226 fields = {"projectName" : project , "versionName" : version }
214227 response = bd .session .post ("/api/scan/data" , files = files , data = fields )
215- logging .info (response )
228+ logging .debug (response )
216229
217230 if response .status_code == 409 :
218- logging .info (f"File { filename } is already mapped to a different project version" )
231+ logging .error (f"File { filename } is already mapped to a different project version" )
219232
220233 if response .status_code != 201 :
221234 logging .error (f"Failed to upload SPDX file:" )
@@ -227,41 +240,23 @@ def upload_sbom_file(filename, project, version):
227240
228241
229242# Lookup the given pURL in the BD KB.
230- # If successfully matched, update the associated package name and version with the data from the KB.
231- # This will improve the accuracy of later lookups. We are replacing the SPDX input data with the
232- # data stored in the KB.
233243#
234244# Inputs:
235- # matchname - Name of package from the SPDX input file
236- # matchver - Version of package
237- # extref - pURL to look up
245+ # extref - pURL to look up
238246#
239247# Returns:
240- # purlmatch - boolean (True if successful KB lookup)
241- # matchname - Original parameter OR updated to reflect KB lookup name
242- # matchver - Original parameter OR updated to reflect KB lookup version
243- def find_comp_in_kb (matchname , matchver , extref ):
244- purlmatch = False
248+ # If match: API matching data (the "result" object)
249+ # No match: None
250+ def find_comp_in_kb (extref ):
245251 params = {
246252 'packageUrl' : extref
247253 }
248254 for result in bd .get_items ("/api/search/purl-components" , params = params ):
249- # This query should result in exactly 1 match
250- purlmatch = True
251- # Override the spdx name and use the known KB name
252- if matchname != result ['componentName' ]:
253- print (f"Renaming { matchname } -> { result ['componentName' ]} " )
254- matchname = result ['componentName' ]
255- # Override the spdx version and use the string from KB
256- # for example, v2.8.5 -> 2.8.5
257- if matchver != result ['versionName' ]:
258- print (f"Renaming { matchver } -> { result ['versionName' ]} " )
259- matchver = result ['versionName' ]
255+ # Should be exactly 1 match when successful
256+ return (result )
260257
261- return (purlmatch , matchname , matchver )
262-
263- # fall through -- lookup failed, so we keep the original name/ver
264- return (purlmatch , matchname , matchver )
258+ # Fall through -- lookup failed
259+ return (None )
265260
266261
267262# Locate component name + version in BOM
@@ -472,8 +467,13 @@ def add_to_sbom(proj_version_url, comp_ver_url):
472467# This will exit if it fails
473468poll_for_upload (document .creation_info .name )
474469
475- # some little debug/test stubs
470+ # some debug/test stubs
476471# TODO: delete these
472+ #ver="https://purl-validation.saas-staging.blackduck.com/api/projects/c2b4463f-7996-4c45-8443-b69b4f82ef1d/versions/67e4f6f5-2f42-42c4-9b69-e39bad55f907"
473+ #comp = "https://purl-validation.saas-staging.blackduck.com/api/components/fc0a76fe-70a4-4afa-9a94-c3c22d63454f/versions/fabaabb9-3b9a-4b5f-850a-39fe84c4cfc4"
474+ #add_to_sbom(ver, comp)
475+ #quit()
476+
477477#matchcomp, matchver = find_cust_comp("ipaddress", "1.0.23")
478478#if matchcomp:
479479# print("matched comp")
@@ -493,12 +493,8 @@ def add_to_sbom(proj_version_url, comp_ver_url):
493493#add_to_sbom(pv, cv)
494494#quit()
495495
496- # Open unmatched component file
497- # Will save name, spdxid, version, and origin/purl for later in json format:
498- # "name": "react-bootstrap",
499- # "spdx_id": "SPDXRef-Pkg-react-bootstrap-2.1.2-30223",
500- # "version": "2.1.2",
501- # "origin": null
496+ # Open unmatched component file to save name, spdxid, version, and
497+ # origin/purl for later in json format
502498# TODO this try/except isn't quite right
503499try : outfile = open (args .out_file , 'w' )
504500except :
@@ -531,16 +527,6 @@ def add_to_sbom(proj_version_url, comp_ver_url):
531527
532528logging .debug (f"Found { project ['name' ]} :{ version ['versionName' ]} " )
533529
534- # situations to consider + actions
535- # 1) No purl available : check SBOM for comp+ver, then add cust comp + add to SBOM
536- # 2) Have purl + found in KB
537- # - In SBOM? -> done
538- # - Else -> add known KB comp to SBOM
539- # *** this shouldn't happen in theory
540- # 3) Have purl + not in KB (main case we are concerned with)
541- # - In SBOM? (maybe already added or whatever?) -> done
542- # - Else -> add cust comp + add to SBOM (same as 1)
543-
544530# Stats to track
545531bom_matches = 0
546532kb_matches = 0
@@ -560,60 +546,81 @@ def add_to_sbom(proj_version_url, comp_ver_url):
560546 purlmatch = False
561547 matchname = package .name
562548 matchver = package .version
549+ print (f"Processing SPDX package: { matchname } { matchver } ...." )
563550 # Tracking unique package name + version from spdx file
564551 packages [matchname + matchver ] = packages .get (matchname + matchver , 0 ) + 1
565552
566- # NOTE: BD can change the original component name
567- # EX: "React" -> "React from Facebook"
568553 if package .external_references :
569- inkb , matchname , matchver = find_comp_in_kb (matchname , matchver , package .external_references [0 ].locator )
570- if inkb : kb_matches += 1
554+ # TODO need to handle the possiblity of:
555+ # A) multiple extrefs
556+ # B) an extref that is not a purl
557+ # -- referenceType should be "purl" - ignore others?
558+ kb_match = find_comp_in_kb (package .external_references [0 ].locator )
559+ if (kb_match ):
560+ # Update package name and version to reflect the KB name/ver
561+ print (f" KB match for { package .name } { package .version } " )
562+ kb_matches += 1
563+ matchname = kb_match ['componentName' ]
564+ matchver = kb_match ['versionName' ]
565+ else :
566+ print (f" No KB match for { package .name } { package .version } " )
571567 else :
572568 nopurl += 1
573- print ("No pURL found for component: " )
574- print (" " + package .name )
575- print (" " + package .spdx_id )
576- print (" " + package .version )
569+ kb_match = None
570+ print (f"No pURL provided for { package .name } { package .version } " )
577571
578572 if find_comp_in_bom (matchname , matchver , version ):
579573 bom_matches += 1
580- print (" Found comp match in BOM: " + matchname + matchver )
574+ print (f" Found component in BOM: { matchname } { matchver } " )
575+ # It's in the BOM so we are happy
576+ # Everything else below is related to adding to the BOM
577+ continue
578+
579+ # If we've gotten this far, the package is not in the BOM.
580+ # Now we need to figure out:
581+ # - Is it already in the KB and we need to add it? (should be rare)
582+ # - Do we need to add a custom component?
583+ # - Do we need to add a version to an existing custom component?
584+ nomatch += 1
585+ print (f" Not present in BOM: { matchname } { matchver } " )
586+ comp_data = {
587+ "name" : package .name ,
588+ "spdx_id" : package .spdx_id ,
589+ "version" : package .version ,
590+ "origin" : extref
591+ }
592+ comps_out .append (comp_data )
593+
594+ # KB match was successful, but it wasn't in the BOM for some reason
595+ if kb_match :
596+ print (f" WARNING: { matchname } { matchver } in KB but not in SBOM" )
597+ add_to_sbom (proj_version_url , kb_match ['version' ])
598+ # temp debug to find this case
599+ quit ()
600+ # short-circuit the rest
601+ continue
602+
603+ # Check if custom component already exists
604+ comp_url , comp_ver_url = find_cust_comp (package .name , package .version )
605+
606+ if not comp_url :
607+ # Custom component did not exist, so create it
608+ cust_comp_count += 1
609+ comp_ver_url = create_cust_comp (package .name , package .version ,
610+ args .license_name )
611+ elif comp_url and not comp_ver_url :
612+ # Custom component existed, but not the version we care about
613+ cust_ver_count += 1
614+ print (f" Adding version { package .version } to custom component { package .name } " )
615+ comp_ver_url = create_cust_comp_ver (comp_url , package .version , args .license_name )
581616 else :
582- nomatch += 1
583- comp_data = {
584- "name" : package .name ,
585- "spdx_id" : package .spdx_id ,
586- "version" : package .version ,
587- "origin" : extref
588- }
589- comps_out .append (comp_data )
590-
591- # TODO what about: KB exists but not in BOM??
592- # find_cust_comp is not generic enough for that situation
593- #if inkb:
594- # TODO handle add KB match to BOM here, short-circuit steps below
617+ print (" Custom component already exists, not in SBOM" )
595618
596- # Check if custom component already exists
597- comp_url , comp_ver_url = find_cust_comp (package .name , package .version )
598-
599- if not comp_url :
600- # Custom component did not exist, so create it
601- cust_comp_count += 1
602- comp_ver_url = create_cust_comp (package .name , package .version ,
603- args .license_name )
604- elif comp_url and not comp_ver_url :
605- # Custom component existed, but not the version we care about
606- cust_ver_count += 1
607- print (f"Adding version { package .version } to custom component { package .name } " )
608- comp_ver_url = create_cust_comp_ver (comp_url , package .version , args .license_name )
609- else :
610- print ("Custom component already exists, not in SBOM" )
619+ # Shouldn't be possible
620+ assert (comp_ver_url ), f"No component URL found for { package .name } { package .version } "
611621
612- # is this possible?
613- assert (comp_ver_url ), f"No comp_ver URL found for { package .name } { package .version } "
614-
615- print (f"Adding component to SBOM: { package .name } aka { matchname } { package .version } " )
616- add_to_sbom (proj_version_url , comp_ver_url )
622+ print (f" Adding component to SBOM: { package .name } aka { matchname } { package .version } " )
623+ add_to_sbom (proj_version_url , comp_ver_url )
617624
618625# Save unmatched components
619626json .dump (comps_out , outfile )
@@ -622,9 +629,9 @@ def add_to_sbom(proj_version_url, comp_ver_url):
622629print ("\n Stats: " )
623630print ("------" )
624631print (f" SPDX packages processed: { package_count } " )
625- print (f" Non matches: { nomatch } " )
626- print (f" KB matches: { kb_matches } " )
632+ print (f" Packages missing from BOM: { nomatch } " )
627633print (f" BOM matches: { bom_matches } " )
634+ print (f" KB matches: { kb_matches } " )
628635print (f" Packages missing purl: { nopurl } " )
629636print (f" Custom components created: { cust_comp_count } " )
630637print (f" Custom component versions created: { cust_ver_count } " )
0 commit comments