eric7/CycloneDXInterface/CycloneDXUtilities.py

branch
eric7
changeset 9117
c6afba2049cf
child 9119
5bcdef5207f6
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric7/CycloneDXInterface/CycloneDXUtilities.py	Fri Jun 03 19:54:57 2022 +0200
@@ -0,0 +1,152 @@
+# -*- 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)
+        )

eric ide

mercurial