Fri, 03 Jun 2022 19:54:57 +0200
CycloneDX Interface
- added capability to create a Software Bill of Materials (SBOM) file in CycloneDX format
- added this capability to the pip Interface and Project
# -*- coding: utf-8 -*- # Copyright (c) 2022 Detlev Offenbach <detlev@die-offenbachs.de> # """ Module implementing the interface to CycloneDX. """ import os from PyQt6.QtCore import QCoreApplication from PyQt6.QtWidgets import QDialog from EricWidgets.EricApplication import ericApp from EricWidgets import EricMessageBox from packageurl import PackageURL from cyclonedx.model import LicenseChoice from cyclonedx.model.bom import Bom from cyclonedx.model.component import Component from cyclonedx.output import ( OutputFormat, SchemaVersion, get_instance as get_output_instance ) from cyclonedx.parser import BaseParser from cyclonedx_py.parser.pipenv import PipEnvFileParser from cyclonedx_py.parser.poetry import PoetryFileParser from cyclonedx_py.parser.requirements import RequirementsFileParser class CycloneDXEnvironmentParser(BaseParser): """ Class implementing a parser to get package data for a named environment. """ def __init__(self, venvName): """ Constructor @param venvName name of the virtual environment @type str """ super().__init__() pip = ericApp().getObject("Pip") packages = pip.getLicenses(venvName) for package in packages: comp = Component( name=package["Name"], version=package["Version"], author=package["Author"], description=package["Description"], purl=PackageURL( type='pypi', name=package["Name"], version=package["Version"] ) ) for lic in package["License"].split(";"): comp.licenses.add( LicenseChoice(license_expression=lic.strip()) ) self._components.append(comp) def createCycloneDXFile(venvName): """ Function to create a CyccloneDX SBOM file. @param venvName name of the virtual environment @type str @exception RuntimeError raised to indicate illegal creation parameters """ from .CycloneDXConfigDialog import CycloneDXConfigDialog dlg = CycloneDXConfigDialog(venvName) if dlg.exec() == QDialog.DialogCode.Accepted: inputSource, inputFile, fileFormat, schemaVersion, sbomFile = ( dlg.getData() ) if inputSource not in ("environment", "pipenv", "poetry", "requirements"): raise RuntimeError("Unsupported input source given.") if fileFormat not in ("XML", "JSON"): raise RuntimeError("Unsupported SBOM file format given.") if inputSource == "environment": parser = CycloneDXEnvironmentParser(venvName) else: # all other parsers need an input file if not os.path.isfile(inputFile): EricMessageBox.warning( None, QCoreApplication.translate( "CycloneDX", "CycloneDX - SBOM Creation"), QCoreApplication.translate( "CycloneDX", "<p>The configured input file <b>{0}</b> does not" " exist. Aborting...</p>" ).format(inputFile) ) return if inputSource == "pipenv": parser = PipEnvFileParser(pipenv_lock_filename=inputFile) elif inputSource == "poetry": parser = PoetryFileParser(poetry_lock_filename=inputFile) elif inputSource == "requirements": parser = RequirementsFileParser(requirements_file=inputFile) if fileFormat == "XML": outputFormat = OutputFormat.XML elif fileFormat == "JSON": outputFormat = OutputFormat.JSON if parser.has_warnings(): excludedList = ["<li>{0}</li>".format(warning.get_item()) for warning in parser.get_warnings()] EricMessageBox.warning( None, QCoreApplication.translate( "CycloneDX", "CycloneDX - SBOM Creation"), QCoreApplication.translate( "CycloneDX", "<p>Some of the dependencies do not have pinned version" " numbers.<ul>{0}</ul>The above listed packages will NOT" " be included in the generated CycloneDX SBOM file as" " version is a mandatory field.</p>" ).format("".join(excludedList)) ) bom = Bom.from_parser(parser=parser) output = get_output_instance( bom=bom, output_format=outputFormat, schema_version=SchemaVersion['V{0}'.format( schemaVersion.replace('.', '_') )] ) output.output_to_file(filename=sbomFile, allow_overwrite=True) EricMessageBox.information( None, QCoreApplication.translate( "CycloneDX", "CycloneDX - SBOM Creation"), QCoreApplication.translate( "CycloneDX", "<p>The SBOM data was written to file <b>{0}</b>.</p>" ).format(sbomFile) )