From 8cecd1298dfb3328b44d88b3f41dacb3e51f1f80 Mon Sep 17 00:00:00 2001 From: Gernot Hillier Date: Sat, 19 Aug 2023 07:40:33 +0200 Subject: [PATCH] fix(project CreateBom): decode multiple purls and warn user Fix #36 --- ChangeLog.md | 5 +++-- capycli/project/create_bom.py | 7 +++++++ tests/test_create_bom.py | 38 +++++++++++++++++++++++++++++++++++ 3 files changed, 48 insertions(+), 2 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 137b905..d7e05f8 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -10,7 +10,8 @@ * Be more resilient about missing metadata in CycloneDX SBOMs. * The `-o` parameter of the command `project GetLicenseInfo` is now optional. But you still need this output when you want to create a Readme. -* `project createbom` add purl, source and repository url from SW360 if available +* `project createbom` add purls, source and repository url from SW360 if available. + If multiple purls are found, a warning is printed asking user to manually edit SBOM. * `project createbom` add SW360 source and binary attachments as external reference to SBOM. * `project createbom` adds SW360 project name, version and description to SBOM. @@ -27,7 +28,7 @@ * `bom map` will report matches by name, but different version **only** if `-all` has been specified. The original idea of CaPyCLI was to report as many potential matches as possible and to let the user decide which match to take by editing the SBOM. But it seems that many users did not read the documentation - and the expectations were different. Therefore the default behavior has been changed. + and the expectations were different. Therefore the default behavior has been changed. The original behavior of versions prior to 2.x can be enabled via the `-all` switch. ## 2.0.0.dev (2023-05-19) diff --git a/capycli/project/create_bom.py b/capycli/project/create_bom.py index c22817b..4834729 100644 --- a/capycli/project/create_bom.py +++ b/capycli/project/create_bom.py @@ -17,6 +17,7 @@ import capycli.common.script_base from capycli import get_logger from capycli.common.capycli_bom_support import CaPyCliBom, CycloneDxSupport, SbomCreator +from capycli.common.purl_utils import PurlUtils from capycli.common.print import print_red, print_text, print_yellow from capycli.main.result_codes import ResultCode @@ -59,6 +60,12 @@ def create_project_bom(self, project) -> list: # try another id name purl = self.get_external_id("purl", release_details) + purl = PurlUtils.parse_purls_from_external_id(purl) + if len(purl) > 1: + print_yellow(" Multiple purls added for", release["name"], release["version"]) + print_yellow(" You must remove all but one in your SBOM!") + purl = " ".join(purl) + if purl: rel_item = Component(name=release["name"], version=release["version"], purl=purl, bom_ref=purl) else: diff --git a/tests/test_create_bom.py b/tests/test_create_bom.py index b23011c..da14515 100644 --- a/tests/test_create_bom.py +++ b/tests/test_create_bom.py @@ -126,6 +126,44 @@ def test_project_not_found(self) -> None: except SystemExit as ex: self.assertEqual(ResultCode.RESULT_ERROR_ACCESSING_SW360, ex.code) + @responses.activate + def test_create_bom_multiple_purls(self): + sut = CreateBom() + + self.add_login_response() + sut.login(token=TestBase.MYTOKEN, url=TestBase.MYURL) + + # the first release + responses.add( + responses.GET, + url=self.MYURL + "resource/api/releases/r001", + json=self.get_release_wheel_for_test(), + status=200, + content_type="application/json", + adding_headers={"Authorization": "Token " + self.MYTOKEN}, + ) + + # the second release + release = self.get_release_cli_for_test() + # use a specific purl + release["externalIds"]["package-url"] = "[\"pkg:deb/debian/cli-support@1.3-1\",\"pkg:pypi/cli-support@1.3\"]" + responses.add( + responses.GET, + url=self.MYURL + "resource/api/releases/r002", + json=release, + status=200, + content_type="application/json", + adding_headers={"Authorization": "Token " + self.MYTOKEN}, + ) + + out = self.capture_stdout(sut.create_project_bom, self.get_project_for_test()) + self.assertIn("Multiple purls added", out) + + # TODO self.capture_stdout doesn't allow us to get return value, + # so re-run test. See also https://github.com/sw360/capycli/issues/39 + cdx_components = sut.create_project_bom(self.get_project_for_test()) + self.assertEqual(cdx_components[0].purl, "pkg:deb/debian/cli-support@1.3-1 pkg:pypi/cli-support@1.3") + @responses.activate def test_project_by_id(self): sut = CreateBom()