capycli 2.8.0__tar.gz → 2.9.0.dev1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {capycli-2.8.0 → capycli-2.9.0.dev1}/PKG-INFO +1 -1
- {capycli-2.8.0 → capycli-2.9.0.dev1}/capycli/__init__.py +6 -0
- {capycli-2.8.0 → capycli-2.9.0.dev1}/capycli/bom/bom_convert.py +1 -1
- {capycli-2.8.0 → capycli-2.9.0.dev1}/capycli/bom/bom_validate.py +1 -1
- {capycli-2.8.0 → capycli-2.9.0.dev1}/capycli/bom/check_bom.py +1 -1
- {capycli-2.8.0 → capycli-2.9.0.dev1}/capycli/bom/check_bom_item_status.py +1 -1
- {capycli-2.8.0 → capycli-2.9.0.dev1}/capycli/bom/check_granularity.py +1 -1
- {capycli-2.8.0 → capycli-2.9.0.dev1}/capycli/bom/create_components.py +11 -2
- {capycli-2.8.0 → capycli-2.9.0.dev1}/capycli/bom/diff_bom.py +1 -1
- {capycli-2.8.0 → capycli-2.9.0.dev1}/capycli/bom/download_sources.py +1 -1
- {capycli-2.8.0 → capycli-2.9.0.dev1}/capycli/bom/filter_bom.py +1 -1
- {capycli-2.8.0 → capycli-2.9.0.dev1}/capycli/bom/findsources.py +5 -3
- {capycli-2.8.0 → capycli-2.9.0.dev1}/capycli/bom/map_bom.py +59 -69
- {capycli-2.8.0 → capycli-2.9.0.dev1}/capycli/bom/merge_bom.py +1 -1
- {capycli-2.8.0 → capycli-2.9.0.dev1}/capycli/bom/show_bom.py +1 -1
- {capycli-2.8.0 → capycli-2.9.0.dev1}/capycli/common/map_result.py +33 -22
- {capycli-2.8.0 → capycli-2.9.0.dev1}/capycli/common/purl_service.py +22 -83
- {capycli-2.8.0 → capycli-2.9.0.dev1}/capycli/dependencies/javascript.py +1 -1
- {capycli-2.8.0 → capycli-2.9.0.dev1}/capycli/dependencies/maven_list.py +1 -1
- {capycli-2.8.0 → capycli-2.9.0.dev1}/capycli/dependencies/maven_pom.py +1 -1
- capycli-2.9.0.dev1/capycli/dependencies/nuget.py +714 -0
- {capycli-2.8.0 → capycli-2.9.0.dev1}/capycli/dependencies/python.py +1 -1
- {capycli-2.8.0 → capycli-2.9.0.dev1}/capycli/main/options.py +1 -1
- {capycli-2.8.0 → capycli-2.9.0.dev1}/capycli/mapping/mapping_to_html.py +1 -1
- {capycli-2.8.0 → capycli-2.9.0.dev1}/capycli/mapping/mapping_to_xlsx.py +1 -1
- {capycli-2.8.0 → capycli-2.9.0.dev1}/capycli/moverview/moverview_to_html.py +1 -1
- {capycli-2.8.0 → capycli-2.9.0.dev1}/capycli/moverview/moverview_to_xlsx.py +1 -1
- {capycli-2.8.0 → capycli-2.9.0.dev1}/capycli/project/check_prerequisites.py +1 -1
- {capycli-2.8.0 → capycli-2.9.0.dev1}/capycli/project/create_bom.py +1 -1
- {capycli-2.8.0 → capycli-2.9.0.dev1}/capycli/project/create_project.py +16 -1
- {capycli-2.8.0 → capycli-2.9.0.dev1}/capycli/project/create_readme.py +1 -1
- {capycli-2.8.0 → capycli-2.9.0.dev1}/capycli/project/find_project.py +1 -1
- {capycli-2.8.0 → capycli-2.9.0.dev1}/capycli/project/get_license_info.py +1 -1
- {capycli-2.8.0 → capycli-2.9.0.dev1}/capycli/project/show_ecc.py +1 -1
- {capycli-2.8.0 → capycli-2.9.0.dev1}/capycli/project/show_licenses.py +1 -1
- {capycli-2.8.0 → capycli-2.9.0.dev1}/capycli/project/show_project.py +1 -1
- {capycli-2.8.0 → capycli-2.9.0.dev1}/capycli/project/show_vulnerabilities.py +1 -1
- {capycli-2.8.0 → capycli-2.9.0.dev1}/pyproject.toml +1 -1
- capycli-2.8.0/capycli/dependencies/nuget.py +0 -185
- {capycli-2.8.0 → capycli-2.9.0.dev1}/LICENSES/CC0-1.0.txt +0 -0
- {capycli-2.8.0 → capycli-2.9.0.dev1}/LICENSES/MIT.txt +0 -0
- {capycli-2.8.0 → capycli-2.9.0.dev1}/License.md +0 -0
- {capycli-2.8.0 → capycli-2.9.0.dev1}/Readme.md +0 -0
- {capycli-2.8.0 → capycli-2.9.0.dev1}/capycli/__main__.py +0 -0
- {capycli-2.8.0 → capycli-2.9.0.dev1}/capycli/bom/__init__.py +0 -0
- {capycli-2.8.0 → capycli-2.9.0.dev1}/capycli/bom/csv.py +0 -0
- {capycli-2.8.0 → capycli-2.9.0.dev1}/capycli/bom/handle_bom.py +0 -0
- {capycli-2.8.0 → capycli-2.9.0.dev1}/capycli/bom/html.py +0 -0
- {capycli-2.8.0 → capycli-2.9.0.dev1}/capycli/bom/legacy.py +0 -0
- {capycli-2.8.0 → capycli-2.9.0.dev1}/capycli/bom/legacy_cx.py +0 -0
- {capycli-2.8.0 → capycli-2.9.0.dev1}/capycli/bom/plaintext.py +0 -0
- {capycli-2.8.0 → capycli-2.9.0.dev1}/capycli/common/__init__.py +0 -0
- {capycli-2.8.0 → capycli-2.9.0.dev1}/capycli/common/capycli_bom_support.py +0 -0
- {capycli-2.8.0 → capycli-2.9.0.dev1}/capycli/common/comparable_version.py +0 -0
- {capycli-2.8.0 → capycli-2.9.0.dev1}/capycli/common/component_cache.py +0 -0
- {capycli-2.8.0 → capycli-2.9.0.dev1}/capycli/common/dependencies_base.py +0 -0
- {capycli-2.8.0 → capycli-2.9.0.dev1}/capycli/common/file_support.py +0 -0
- {capycli-2.8.0 → capycli-2.9.0.dev1}/capycli/common/github_support.py +0 -0
- {capycli-2.8.0 → capycli-2.9.0.dev1}/capycli/common/html_support.py +0 -0
- {capycli-2.8.0 → capycli-2.9.0.dev1}/capycli/common/json_support.py +0 -0
- {capycli-2.8.0 → capycli-2.9.0.dev1}/capycli/common/print.py +0 -0
- {capycli-2.8.0 → capycli-2.9.0.dev1}/capycli/common/purl_store.py +0 -0
- {capycli-2.8.0 → capycli-2.9.0.dev1}/capycli/common/purl_utils.py +0 -0
- {capycli-2.8.0 → capycli-2.9.0.dev1}/capycli/common/script_base.py +0 -0
- {capycli-2.8.0 → capycli-2.9.0.dev1}/capycli/common/script_support.py +0 -0
- {capycli-2.8.0 → capycli-2.9.0.dev1}/capycli/data/__init__.py +0 -0
- {capycli-2.8.0 → capycli-2.9.0.dev1}/capycli/data/granularity_list.csv +0 -0
- {capycli-2.8.0 → capycli-2.9.0.dev1}/capycli/dependencies/__init__.py +0 -0
- {capycli-2.8.0 → capycli-2.9.0.dev1}/capycli/dependencies/handle_dependencies.py +0 -0
- {capycli-2.8.0 → capycli-2.9.0.dev1}/capycli/main/__init__.py +0 -0
- {capycli-2.8.0 → capycli-2.9.0.dev1}/capycli/main/application.py +0 -0
- {capycli-2.8.0 → capycli-2.9.0.dev1}/capycli/main/argument_parser.py +0 -0
- {capycli-2.8.0 → capycli-2.9.0.dev1}/capycli/main/cli.py +0 -0
- {capycli-2.8.0 → capycli-2.9.0.dev1}/capycli/main/exceptions.py +0 -0
- {capycli-2.8.0 → capycli-2.9.0.dev1}/capycli/main/result_codes.py +0 -0
- {capycli-2.8.0 → capycli-2.9.0.dev1}/capycli/mapping/handle_mapping.py +0 -0
- {capycli-2.8.0 → capycli-2.9.0.dev1}/capycli/moverview/handle_moverview.py +0 -0
- {capycli-2.8.0 → capycli-2.9.0.dev1}/capycli/project/__init__.py +0 -0
- {capycli-2.8.0 → capycli-2.9.0.dev1}/capycli/project/handle_project.py +0 -0
|
@@ -63,6 +63,12 @@ def get_app_version() -> str:
|
|
|
63
63
|
return version
|
|
64
64
|
|
|
65
65
|
|
|
66
|
+
def get_app_signature() -> str:
|
|
67
|
+
"""Get the signature of this application."""
|
|
68
|
+
version = get_app_version()
|
|
69
|
+
return f"{APP_NAME}, {version}"
|
|
70
|
+
|
|
71
|
+
|
|
66
72
|
# There is nothing lower than logging.DEBUG (10) in the logging library,
|
|
67
73
|
# but we want an extra level to avoid being too verbose when using -vv.
|
|
68
74
|
_EXTRA_VERBOSE = 5
|
|
@@ -163,7 +163,7 @@ class BomConvert(capycli.common.script_base.ScriptBase):
|
|
|
163
163
|
|
|
164
164
|
def run(self, args: Any) -> None:
|
|
165
165
|
"""Main method()"""
|
|
166
|
-
print("\n" + capycli.
|
|
166
|
+
print("\n" + capycli.get_app_signature() + " - Convert SBOM formats\n")
|
|
167
167
|
|
|
168
168
|
if args.help:
|
|
169
169
|
self.display_help()
|
|
@@ -61,7 +61,7 @@ class BomValidate(capycli.common.script_base.ScriptBase):
|
|
|
61
61
|
|
|
62
62
|
def run(self, args: Any) -> None:
|
|
63
63
|
"""Main method()"""
|
|
64
|
-
print("\n" + capycli.
|
|
64
|
+
print("\n" + capycli.get_app_signature() + " - Validate a CaPyCLI/CycloneDX SBOM\n")
|
|
65
65
|
|
|
66
66
|
if args.help:
|
|
67
67
|
self.display_help()
|
|
@@ -158,7 +158,7 @@ class CheckBom(capycli.common.script_base.ScriptBase):
|
|
|
158
158
|
logging.getLogger("urllib3.connectionpool").setLevel(logging.WARNING)
|
|
159
159
|
|
|
160
160
|
print_text(
|
|
161
|
-
"\n" + capycli.
|
|
161
|
+
"\n" + capycli.get_app_signature() +
|
|
162
162
|
" - Check that all releases in the SBOM exist on target SW360 instance.\n")
|
|
163
163
|
|
|
164
164
|
if args.help:
|
|
@@ -176,7 +176,7 @@ class CheckBomItemStatus(capycli.common.script_base.ScriptBase):
|
|
|
176
176
|
logging.getLogger("urllib3.connectionpool").setLevel(logging.WARNING)
|
|
177
177
|
|
|
178
178
|
print_text(
|
|
179
|
-
"\n" + capycli.
|
|
179
|
+
"\n" + capycli.get_app_signature()
|
|
180
180
|
+ " - check the status of the items on SW360\n")
|
|
181
181
|
|
|
182
182
|
if args.help:
|
|
@@ -224,7 +224,7 @@ class CheckGranularity(capycli.common.script_base.ScriptBase):
|
|
|
224
224
|
LOG = capycli.get_logger(__name__)
|
|
225
225
|
|
|
226
226
|
print_text(
|
|
227
|
-
"\n" + capycli.
|
|
227
|
+
"\n" + capycli.get_app_signature() +
|
|
228
228
|
" - Check the granularity of all releases in the SBOM.\n")
|
|
229
229
|
|
|
230
230
|
if args.help:
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# -------------------------------------------------------------------------------
|
|
2
|
-
# Copyright (c) 2019-
|
|
2
|
+
# Copyright (c) 2019-2025 Siemens
|
|
3
3
|
# All Rights Reserved.
|
|
4
4
|
# Author: thomas.graf@siemens.com
|
|
5
5
|
#
|
|
@@ -259,6 +259,10 @@ class BomCreateComponents(capycli.common.script_base.ScriptBase):
|
|
|
259
259
|
# ensure that we have the only correct external-id name: package-url
|
|
260
260
|
data["externalIds"]["package-url"] = cx_comp.purl.to_string()
|
|
261
261
|
|
|
262
|
+
# add information that this release was created by CaPyCli
|
|
263
|
+
data["additionalData"] = {}
|
|
264
|
+
data["additionalData"]["createdWith"] = capycli.get_app_signature()
|
|
265
|
+
|
|
262
266
|
# use project site as fallback for source code download url
|
|
263
267
|
website = CycloneDxSupport.get_ext_ref_website(cx_comp)
|
|
264
268
|
repo = CycloneDxSupport.get_ext_ref_repository(cx_comp)
|
|
@@ -318,6 +322,11 @@ class BomCreateComponents(capycli.common.script_base.ScriptBase):
|
|
|
318
322
|
if cx_comp.purl:
|
|
319
323
|
purl = PurlUtils.component_purl_from_release_purl(cx_comp.purl)
|
|
320
324
|
data["externalIds"] = {"package-url": purl}
|
|
325
|
+
|
|
326
|
+
# add information that this component was created by CaPyCli
|
|
327
|
+
data["additionalData"] = {}
|
|
328
|
+
data["additionalData"]["createdWith"] = capycli.get_app_signature()
|
|
329
|
+
|
|
321
330
|
return data
|
|
322
331
|
|
|
323
332
|
def create_release(self, cx_comp: Component, component_id: str) -> Optional[Dict[str, Any]]:
|
|
@@ -733,7 +742,7 @@ class BomCreateComponents(capycli.common.script_base.ScriptBase):
|
|
|
733
742
|
logging.getLogger("urllib3.connectionpool").setLevel(logging.WARNING)
|
|
734
743
|
|
|
735
744
|
print_text(
|
|
736
|
-
"\n" + capycli.
|
|
745
|
+
"\n" + capycli.get_app_signature() +
|
|
737
746
|
" - Create new components and releases on SW360\n")
|
|
738
747
|
|
|
739
748
|
if args.help:
|
|
@@ -202,7 +202,7 @@ class DiffBom(capycli.common.script_base.ScriptBase):
|
|
|
202
202
|
LOG = capycli.get_logger(__name__)
|
|
203
203
|
|
|
204
204
|
print_text(
|
|
205
|
-
"\n" + capycli.
|
|
205
|
+
"\n" + capycli.get_app_signature() +
|
|
206
206
|
" - Compare two SBOM files.\n")
|
|
207
207
|
|
|
208
208
|
if args.help:
|
|
@@ -158,7 +158,7 @@ class BomDownloadSources(capycli.common.script_base.ScriptBase):
|
|
|
158
158
|
logging.getLogger("urllib3.connectionpool").setLevel(logging.WARNING)
|
|
159
159
|
|
|
160
160
|
print_text(
|
|
161
|
-
"\n" + capycli.
|
|
161
|
+
"\n" + capycli.get_app_signature() +
|
|
162
162
|
" - Download source files from the URL specified in the SBOM\n")
|
|
163
163
|
|
|
164
164
|
if args.help:
|
|
@@ -271,7 +271,7 @@ class FilterBom(capycli.common.script_base.ScriptBase):
|
|
|
271
271
|
global LOG
|
|
272
272
|
LOG = capycli.get_logger(__name__)
|
|
273
273
|
|
|
274
|
-
print_text("\n" + capycli.
|
|
274
|
+
print_text("\n" + capycli.get_app_signature() + " - Apply a filter file to a SBOM\n")
|
|
275
275
|
|
|
276
276
|
if args.help:
|
|
277
277
|
print("Usage: CaPyCli bom filter [-h] [-v] -i INPUTFILE -o OUTPUTFILE -filterfile FILTERFILE")
|
|
@@ -232,12 +232,14 @@ class FindSources(capycli.common.script_base.ScriptBase):
|
|
|
232
232
|
w_prefix = [w_prefix]
|
|
233
233
|
|
|
234
234
|
# ORDER BY tag-name-length DESC
|
|
235
|
-
|
|
235
|
+
# Note: it may happen that the GithHubSupport.github_request
|
|
236
|
+
# returns items without 'ref'
|
|
237
|
+
by_size = sorted([(len(tag.get('ref', '')), tag) for tag in w_prefix],
|
|
236
238
|
key=lambda x: x[0])
|
|
237
239
|
w_prefix = [itm[1] for itm in reversed(by_size)]
|
|
238
240
|
|
|
239
241
|
transformed_for_get_matching_tags = [
|
|
240
|
-
{'name': tag
|
|
242
|
+
{'name': tag.get('ref', '').replace('refs/tags/', '', 1),
|
|
241
243
|
'zipball_url': tag['url'].replace(
|
|
242
244
|
'/git/refs/tags/', '/zipball/refs/tags/', 1),
|
|
243
245
|
} for tag in w_prefix]
|
|
@@ -692,7 +694,7 @@ class FindSources(capycli.common.script_base.ScriptBase):
|
|
|
692
694
|
logging.getLogger("urllib3.connectionpool").setLevel(logging.WARNING)
|
|
693
695
|
|
|
694
696
|
print_text(
|
|
695
|
-
"\n" + capycli.
|
|
697
|
+
"\n" + capycli.get_app_signature() +
|
|
696
698
|
" - Go through the list of SBOM items and try to determine the source code.\n")
|
|
697
699
|
|
|
698
700
|
if args.help:
|
|
@@ -14,7 +14,7 @@ import re
|
|
|
14
14
|
import sys
|
|
15
15
|
import urllib
|
|
16
16
|
from enum import Enum
|
|
17
|
-
from typing import Any, Dict, List, Optional
|
|
17
|
+
from typing import Any, Dict, List, Optional
|
|
18
18
|
|
|
19
19
|
from cyclonedx.model import ExternalReference, ExternalReferenceType, XsUri
|
|
20
20
|
from cyclonedx.model.bom import Bom
|
|
@@ -183,24 +183,22 @@ class MapBom(capycli.common.script_base.ScriptBase):
|
|
|
183
183
|
def map_bom_item(self, component: Component, check_similar: bool, result_required: bool) -> MapResult:
|
|
184
184
|
"""Maps a single SBOM item to the list of SW360 releases"""
|
|
185
185
|
|
|
186
|
-
result
|
|
186
|
+
result = self.map_bom_commons(component)
|
|
187
|
+
result_release_ids = [r.split("/")[-1] for r in result.release_hrefs]
|
|
188
|
+
result_component_ids = [r.split("/")[-1] for r in result.component_hrefs]
|
|
187
189
|
|
|
188
190
|
for release in self.releases:
|
|
189
191
|
if ("Id" in release) and ("Sw360Id" not in release):
|
|
190
192
|
release["Sw360Id"] = release["Id"]
|
|
191
193
|
|
|
192
194
|
# first check: unique id
|
|
193
|
-
if
|
|
195
|
+
if release["Sw360Id"] in result_release_ids or self.is_id_match(release, component):
|
|
194
196
|
self.add_match_if_better(result, release, MapResult.FULL_MATCH_BY_ID)
|
|
195
197
|
break
|
|
196
198
|
|
|
197
199
|
# second check: name AND version
|
|
198
|
-
if (component.name
|
|
199
|
-
|
|
200
|
-
and release["Name"]):
|
|
201
|
-
if (
|
|
202
|
-
(result.component_id is not None)
|
|
203
|
-
and (result.component_id == release["ComponentId"])):
|
|
200
|
+
if (component.name and release.get("Name")):
|
|
201
|
+
if release["ComponentId"] in result_component_ids:
|
|
204
202
|
name_match = True
|
|
205
203
|
else:
|
|
206
204
|
name_match = component.name.lower() == release["Name"].lower()
|
|
@@ -276,17 +274,38 @@ class MapBom(capycli.common.script_base.ScriptBase):
|
|
|
276
274
|
|
|
277
275
|
def map_bom_item_no_cache(self, component: Component) -> MapResult:
|
|
278
276
|
"""Maps a single SBOM item to SW360 via online checks (no cache!)"""
|
|
277
|
+
|
|
278
|
+
def get_release_details(href: str) -> Optional[Dict[str, Any]]:
|
|
279
|
+
"""Get release data from SW360 for match result"""
|
|
280
|
+
if not self.client:
|
|
281
|
+
return None
|
|
282
|
+
real_release = self.client.get_release_by_url(href)
|
|
283
|
+
if not real_release:
|
|
284
|
+
print_red("Error accessing release " + href)
|
|
285
|
+
return None
|
|
286
|
+
release = ComponentCacheManagement.convert_release_details(self.client, real_release)
|
|
287
|
+
return release
|
|
288
|
+
|
|
279
289
|
if not self.client:
|
|
280
290
|
print_red(" No client!")
|
|
281
291
|
sys.exit(ResultCode.RESULT_ERROR_ACCESSING_SW360)
|
|
282
292
|
|
|
283
|
-
result
|
|
293
|
+
result = self.map_bom_commons(component)
|
|
284
294
|
components = []
|
|
285
|
-
if component_url:
|
|
286
|
-
components.append(component_url)
|
|
287
295
|
|
|
288
|
-
#
|
|
289
|
-
if
|
|
296
|
+
# Handle matches of PURL cache
|
|
297
|
+
if result.release_hrefs:
|
|
298
|
+
for href in result.release_hrefs:
|
|
299
|
+
release = get_release_details(href)
|
|
300
|
+
if release:
|
|
301
|
+
self.add_match_if_better(result, release, MapResult.FULL_MATCH_BY_ID)
|
|
302
|
+
# If we have release matches by PURL, we're done
|
|
303
|
+
return result
|
|
304
|
+
|
|
305
|
+
if result.component_hrefs:
|
|
306
|
+
components += result.component_hrefs
|
|
307
|
+
else:
|
|
308
|
+
# if there's no purl match for components, search by name
|
|
290
309
|
components2 = self.client.get_component_by_name(component.name)
|
|
291
310
|
if not components2:
|
|
292
311
|
return result
|
|
@@ -297,45 +316,30 @@ class MapBom(capycli.common.script_base.ScriptBase):
|
|
|
297
316
|
]
|
|
298
317
|
|
|
299
318
|
for compref in components:
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
if not comp:
|
|
305
|
-
continue
|
|
306
|
-
rel_list = comp["_embedded"].get("sw360:releases", [])
|
|
319
|
+
comp = self.client.get_component_by_url(compref)
|
|
320
|
+
if not comp:
|
|
321
|
+
continue
|
|
322
|
+
rel_list = comp["_embedded"].get("sw360:releases", [])
|
|
307
323
|
|
|
308
324
|
# Sorted alternatives in descending version order
|
|
309
325
|
# Please note: the release list sometimes contain just the href but no version
|
|
310
326
|
try:
|
|
311
327
|
rel_list = sorted(rel_list,
|
|
312
328
|
key=lambda x: "version" in x and ComparableVersion(
|
|
313
|
-
x.get("version", "")), reverse=True)
|
|
329
|
+
x.get("version", "")), reverse=True)
|
|
314
330
|
except ValueError:
|
|
315
331
|
pass # we can live with an unsorted list
|
|
316
332
|
|
|
317
333
|
for relref in rel_list:
|
|
318
334
|
href = relref["_links"]["self"]["href"]
|
|
319
|
-
real_release = self.client.get_release_by_url(href)
|
|
320
|
-
if not real_release:
|
|
321
|
-
print_red("Error accessign release " + href)
|
|
322
|
-
continue
|
|
323
335
|
|
|
324
336
|
# generate proper release for result
|
|
325
|
-
release =
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
release["Version"] = self.cut_off_debian_extras(real_release["version"])
|
|
329
|
-
else:
|
|
330
|
-
release["Version"] = real_release["version"]
|
|
331
|
-
release["ExternalIds"] = real_release.get("externalIds", "")
|
|
332
|
-
release["Id"] = release["Sw360Id"] = self.client.get_id_from_href(href)
|
|
333
|
-
release["ComponentId"] = self.client.get_id_from_href(compref)
|
|
334
|
-
|
|
335
|
-
release = ComponentCacheManagement.convert_release_details(self.client, real_release)
|
|
337
|
+
release = get_release_details(href)
|
|
338
|
+
if not release:
|
|
339
|
+
continue
|
|
336
340
|
|
|
337
341
|
# first check: unique id
|
|
338
|
-
if
|
|
342
|
+
if self.is_id_match(release, component):
|
|
339
343
|
self.add_match_if_better(result, release, MapResult.FULL_MATCH_BY_ID)
|
|
340
344
|
break
|
|
341
345
|
|
|
@@ -446,8 +450,8 @@ class MapBom(capycli.common.script_base.ScriptBase):
|
|
|
446
450
|
continue
|
|
447
451
|
|
|
448
452
|
dataitem: Dict[str, Any] = {}
|
|
449
|
-
if item.
|
|
450
|
-
dataitem["BomItem"] = item.
|
|
453
|
+
if item.input_component:
|
|
454
|
+
dataitem["BomItem"] = item.input_component.name + ", " + (item.input_component.version or "")
|
|
451
455
|
dataitem["ResultCode"] = item.result
|
|
452
456
|
dataitem["ResultText"] = item.map_code_to_string(item.result)
|
|
453
457
|
dataitems.append(dataitem)
|
|
@@ -632,21 +636,17 @@ class MapBom(capycli.common.script_base.ScriptBase):
|
|
|
632
636
|
newbom.register_dependency(newbom.metadata.component, [newitem])
|
|
633
637
|
elif (item.result == MapResult.NO_MATCH
|
|
634
638
|
or not self.is_good_match(item.result)):
|
|
639
|
+
# if we have no good match, add the component we're looking for as well
|
|
635
640
|
|
|
636
641
|
if (self.mode == MapMode.FOUND):
|
|
637
642
|
continue
|
|
638
643
|
|
|
639
|
-
newitem = item.
|
|
644
|
+
newitem = item.input_component
|
|
640
645
|
if newitem:
|
|
641
646
|
CycloneDxSupport.update_or_set_property(
|
|
642
647
|
newitem,
|
|
643
648
|
CycloneDxSupport.CDX_PROP_MAPRESULT,
|
|
644
649
|
MapResult.NO_MATCH)
|
|
645
|
-
if item.component_id is not None:
|
|
646
|
-
CycloneDxSupport.update_or_set_property(
|
|
647
|
-
newitem,
|
|
648
|
-
CycloneDxSupport.CDX_PROP_COMPONENT_ID,
|
|
649
|
-
item.component_id)
|
|
650
650
|
newbom.components.add(newitem)
|
|
651
651
|
|
|
652
652
|
# Sorted alternatives in descending version order
|
|
@@ -657,7 +657,8 @@ class MapBom(capycli.common.script_base.ScriptBase):
|
|
|
657
657
|
|
|
658
658
|
for match_item in item.releases:
|
|
659
659
|
if self.is_good_match(match_item["MapResult"]):
|
|
660
|
-
|
|
660
|
+
# For good matches, merge the input component with the match item
|
|
661
|
+
newitem = self.update_bom_item(item.input_component, match_item)
|
|
661
662
|
else:
|
|
662
663
|
# newitem = match_item
|
|
663
664
|
newitem = self.update_bom_item(None, match_item)
|
|
@@ -680,15 +681,15 @@ class MapBom(capycli.common.script_base.ScriptBase):
|
|
|
680
681
|
|
|
681
682
|
for item in result:
|
|
682
683
|
single_result: Dict[str, Any] = {}
|
|
683
|
-
if not item.
|
|
684
|
+
if not item.input_component:
|
|
684
685
|
continue
|
|
685
686
|
|
|
686
|
-
for prop in item.
|
|
687
|
+
for prop in item.input_component.properties:
|
|
687
688
|
if prop.name == CycloneDxSupport.CDX_PROP_MAPRESULT:
|
|
688
|
-
item.
|
|
689
|
+
item.input_component.properties.remove(prop)
|
|
689
690
|
break
|
|
690
691
|
|
|
691
|
-
single_result["BomItem"] = LegacySupport.cdx_component_to_legacy(item.
|
|
692
|
+
single_result["BomItem"] = LegacySupport.cdx_component_to_legacy(item.input_component)
|
|
692
693
|
single_result["Result"] = item.result
|
|
693
694
|
single_result["Matches"] = []
|
|
694
695
|
for item_match in item.releases:
|
|
@@ -716,34 +717,23 @@ class MapBom(capycli.common.script_base.ScriptBase):
|
|
|
716
717
|
cachefile, True, token, oauth2=oauth2, url=sw360_url)
|
|
717
718
|
return rel_data
|
|
718
719
|
|
|
719
|
-
def map_bom_commons(self, component: Component) ->
|
|
720
|
+
def map_bom_commons(self, component: Component) -> MapResult:
|
|
720
721
|
"""
|
|
721
722
|
Common parts to map from a SBOM component to the SW360 component/release.
|
|
722
723
|
:param bomitem: SBOM component
|
|
723
724
|
:return: MapResult instance, (Optional) release url, (Optional) component url
|
|
724
725
|
"""
|
|
725
|
-
if not self.client:
|
|
726
|
-
print_red(" No client!")
|
|
727
|
-
sys.exit(ResultCode.RESULT_ERROR_ACCESSING_SW360)
|
|
728
|
-
|
|
729
726
|
if self.relaxed_debian_parsing and component.version:
|
|
730
727
|
component.version = self.cut_off_debian_extras(component.version)
|
|
731
728
|
|
|
732
|
-
result =
|
|
729
|
+
result = MapResult(component)
|
|
733
730
|
|
|
734
731
|
# search release and component by purl which is independent of the component cache.
|
|
735
|
-
if
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
else:
|
|
739
|
-
component_href, release_href = self.external_id_svc.search_component_and_release(
|
|
740
|
-
component.purl) # type: ignore
|
|
741
|
-
if component_href:
|
|
742
|
-
result.component_id = self.client.get_id_from_href(component_href)
|
|
743
|
-
if release_href:
|
|
744
|
-
result.release_id = self.client.get_id_from_href(release_href)
|
|
732
|
+
if component.purl:
|
|
733
|
+
result.component_hrefs = self.external_id_svc.search_components_by_purl(component.purl)
|
|
734
|
+
result.release_hrefs = self.external_id_svc.search_releases_by_purl(component.purl)
|
|
745
735
|
|
|
746
|
-
return result
|
|
736
|
+
return result
|
|
747
737
|
|
|
748
738
|
@property
|
|
749
739
|
def external_id_svc(self) -> PurlService:
|
|
@@ -842,7 +832,7 @@ class MapBom(capycli.common.script_base.ScriptBase):
|
|
|
842
832
|
logging.getLogger("urllib3.connectionpool").setLevel(logging.WARNING)
|
|
843
833
|
|
|
844
834
|
print_text(
|
|
845
|
-
"\n" + capycli.
|
|
835
|
+
"\n" + capycli.get_app_signature() +
|
|
846
836
|
" - Map a given SBOM to data on SW360\n")
|
|
847
837
|
|
|
848
838
|
if args.help:
|
|
@@ -122,7 +122,7 @@ class MergeBom(capycli.common.script_base.ScriptBase):
|
|
|
122
122
|
LOG = capycli.get_logger(__name__)
|
|
123
123
|
|
|
124
124
|
print_text(
|
|
125
|
-
"\n" + capycli.
|
|
125
|
+
"\n" + capycli.get_app_signature() +
|
|
126
126
|
" - Merge two SBOM files.\n")
|
|
127
127
|
|
|
128
128
|
if args.help:
|
|
@@ -98,7 +98,7 @@ class ShowBom(capycli.common.script_base.ScriptBase):
|
|
|
98
98
|
global LOG
|
|
99
99
|
LOG = capycli.get_logger(__name__)
|
|
100
100
|
|
|
101
|
-
print_text("\n" + capycli.
|
|
101
|
+
print_text("\n" + capycli.get_app_signature() + " - Print SBOM contents to stdout\n")
|
|
102
102
|
|
|
103
103
|
if args.help:
|
|
104
104
|
print("usage: capycli bom show [-h] -i bomfile")
|
|
@@ -46,31 +46,42 @@ class MapResult:
|
|
|
46
46
|
NO_MATCH = "9-no-match"
|
|
47
47
|
|
|
48
48
|
def __init__(self, component: Optional[Component] = None) -> None:
|
|
49
|
-
self.
|
|
49
|
+
self.input_component: Optional[Component] = component
|
|
50
50
|
self.result: str = MapResult.NO_MATCH
|
|
51
|
-
self.
|
|
52
|
-
self.
|
|
51
|
+
self._component_hrefs: List[str] = []
|
|
52
|
+
self._release_hrefs: List[str] = []
|
|
53
53
|
self.releases: List[Any] = []
|
|
54
54
|
|
|
55
55
|
@property
|
|
56
|
-
def
|
|
57
|
-
return self.
|
|
58
|
-
|
|
59
|
-
@
|
|
60
|
-
def
|
|
61
|
-
self.
|
|
62
|
-
if self.
|
|
63
|
-
|
|
56
|
+
def component_hrefs(self) -> List[str]:
|
|
57
|
+
return self._component_hrefs
|
|
58
|
+
|
|
59
|
+
@component_hrefs.setter
|
|
60
|
+
def component_hrefs(self, value: List[str]) -> None:
|
|
61
|
+
self._component_hrefs = value
|
|
62
|
+
if not self.input_component:
|
|
63
|
+
return
|
|
64
|
+
if len(value) == 1:
|
|
65
|
+
CycloneDxSupport.update_or_set_property(self.input_component, CycloneDxSupport.CDX_PROP_COMPONENT_ID,
|
|
66
|
+
value[0].split("/")[-1])
|
|
67
|
+
else:
|
|
68
|
+
CycloneDxSupport.remove_property(self.input_component,
|
|
69
|
+
CycloneDxSupport.CDX_PROP_COMPONENT_ID)
|
|
64
70
|
|
|
65
71
|
@property
|
|
66
|
-
def
|
|
67
|
-
return self.
|
|
68
|
-
|
|
69
|
-
@
|
|
70
|
-
def
|
|
71
|
-
self.
|
|
72
|
-
if self.
|
|
73
|
-
|
|
72
|
+
def release_hrefs(self) -> List[str]:
|
|
73
|
+
return self._release_hrefs
|
|
74
|
+
|
|
75
|
+
@release_hrefs.setter
|
|
76
|
+
def release_hrefs(self, value: List[str]) -> None:
|
|
77
|
+
self._release_hrefs = value
|
|
78
|
+
if not self.input_component:
|
|
79
|
+
return
|
|
80
|
+
if len(value) == 1:
|
|
81
|
+
CycloneDxSupport.update_or_set_property(self.input_component, CycloneDxSupport.CDX_PROP_SW360ID,
|
|
82
|
+
value[0].split("/")[-1])
|
|
83
|
+
else:
|
|
84
|
+
CycloneDxSupport.remove_property(self.input_component, CycloneDxSupport.CDX_PROP_SW360ID)
|
|
74
85
|
|
|
75
86
|
@classmethod
|
|
76
87
|
def map_code_to_string(cls, map_code: str) -> str:
|
|
@@ -109,7 +120,7 @@ class MapResult:
|
|
|
109
120
|
+ more
|
|
110
121
|
)
|
|
111
122
|
|
|
112
|
-
if not self.
|
|
123
|
+
if not self.input_component:
|
|
113
124
|
return (
|
|
114
125
|
self.map_code_to_string(self.result)
|
|
115
126
|
+ ", (no component), "
|
|
@@ -119,9 +130,9 @@ class MapResult:
|
|
|
119
130
|
return (
|
|
120
131
|
self.map_code_to_string(self.result)
|
|
121
132
|
+ ", "
|
|
122
|
-
+ str(self.
|
|
133
|
+
+ str(self.input_component.name)
|
|
123
134
|
+ ", "
|
|
124
|
-
+ str(self.
|
|
135
|
+
+ str(self.input_component.version or "")
|
|
125
136
|
+ " "
|
|
126
137
|
+ str(rel)
|
|
127
138
|
)
|