capycli 2.6.0__tar.gz → 2.6.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.6.0 → capycli-2.6.0.dev1}/PKG-INFO +2 -9
- {capycli-2.6.0 → capycli-2.6.0.dev1}/Readme.md +1 -7
- {capycli-2.6.0 → capycli-2.6.0.dev1}/capycli/bom/bom_convert.py +11 -23
- {capycli-2.6.0 → capycli-2.6.0.dev1}/capycli/bom/create_components.py +6 -4
- {capycli-2.6.0 → capycli-2.6.0.dev1}/capycli/bom/filter_bom.py +1 -2
- {capycli-2.6.0 → capycli-2.6.0.dev1}/capycli/bom/findsources.py +1 -1
- {capycli-2.6.0 → capycli-2.6.0.dev1}/capycli/bom/handle_bom.py +4 -12
- {capycli-2.6.0 → capycli-2.6.0.dev1}/capycli/bom/merge_bom.py +0 -26
- {capycli-2.6.0 → capycli-2.6.0.dev1}/capycli/common/capycli_bom_support.py +13 -126
- {capycli-2.6.0 → capycli-2.6.0.dev1}/capycli/dependencies/python.py +4 -5
- {capycli-2.6.0 → capycli-2.6.0.dev1}/capycli/main/options.py +4 -7
- {capycli-2.6.0 → capycli-2.6.0.dev1}/pyproject.toml +1 -2
- capycli-2.6.0/capycli/bom/bom_validate.py +0 -68
- {capycli-2.6.0 → capycli-2.6.0.dev1}/LICENSES/CC0-1.0.txt +0 -0
- {capycli-2.6.0 → capycli-2.6.0.dev1}/LICENSES/MIT.txt +0 -0
- {capycli-2.6.0 → capycli-2.6.0.dev1}/License.md +0 -0
- {capycli-2.6.0 → capycli-2.6.0.dev1}/capycli/__init__.py +0 -0
- {capycli-2.6.0 → capycli-2.6.0.dev1}/capycli/__main__.py +0 -0
- {capycli-2.6.0 → capycli-2.6.0.dev1}/capycli/bom/__init__.py +0 -0
- {capycli-2.6.0 → capycli-2.6.0.dev1}/capycli/bom/check_bom.py +0 -0
- {capycli-2.6.0 → capycli-2.6.0.dev1}/capycli/bom/check_bom_item_status.py +0 -0
- {capycli-2.6.0 → capycli-2.6.0.dev1}/capycli/bom/check_granularity.py +0 -0
- {capycli-2.6.0 → capycli-2.6.0.dev1}/capycli/bom/csv.py +0 -0
- {capycli-2.6.0 → capycli-2.6.0.dev1}/capycli/bom/diff_bom.py +0 -0
- {capycli-2.6.0 → capycli-2.6.0.dev1}/capycli/bom/download_sources.py +0 -0
- {capycli-2.6.0 → capycli-2.6.0.dev1}/capycli/bom/html.py +0 -0
- {capycli-2.6.0 → capycli-2.6.0.dev1}/capycli/bom/legacy.py +0 -0
- {capycli-2.6.0 → capycli-2.6.0.dev1}/capycli/bom/legacy_cx.py +0 -0
- {capycli-2.6.0 → capycli-2.6.0.dev1}/capycli/bom/map_bom.py +0 -0
- {capycli-2.6.0 → capycli-2.6.0.dev1}/capycli/bom/plaintext.py +0 -0
- {capycli-2.6.0 → capycli-2.6.0.dev1}/capycli/bom/show_bom.py +0 -0
- {capycli-2.6.0 → capycli-2.6.0.dev1}/capycli/common/__init__.py +0 -0
- {capycli-2.6.0 → capycli-2.6.0.dev1}/capycli/common/comparable_version.py +0 -0
- {capycli-2.6.0 → capycli-2.6.0.dev1}/capycli/common/component_cache.py +0 -0
- {capycli-2.6.0 → capycli-2.6.0.dev1}/capycli/common/dependencies_base.py +0 -0
- {capycli-2.6.0 → capycli-2.6.0.dev1}/capycli/common/file_support.py +0 -0
- {capycli-2.6.0 → capycli-2.6.0.dev1}/capycli/common/html_support.py +0 -0
- {capycli-2.6.0 → capycli-2.6.0.dev1}/capycli/common/json_support.py +0 -0
- {capycli-2.6.0 → capycli-2.6.0.dev1}/capycli/common/map_result.py +0 -0
- {capycli-2.6.0 → capycli-2.6.0.dev1}/capycli/common/print.py +0 -0
- {capycli-2.6.0 → capycli-2.6.0.dev1}/capycli/common/purl_service.py +0 -0
- {capycli-2.6.0 → capycli-2.6.0.dev1}/capycli/common/purl_store.py +0 -0
- {capycli-2.6.0 → capycli-2.6.0.dev1}/capycli/common/purl_utils.py +0 -0
- {capycli-2.6.0 → capycli-2.6.0.dev1}/capycli/common/script_base.py +0 -0
- {capycli-2.6.0 → capycli-2.6.0.dev1}/capycli/common/script_support.py +0 -0
- {capycli-2.6.0 → capycli-2.6.0.dev1}/capycli/data/__init__.py +0 -0
- {capycli-2.6.0 → capycli-2.6.0.dev1}/capycli/data/granularity_list.csv +0 -0
- {capycli-2.6.0 → capycli-2.6.0.dev1}/capycli/dependencies/__init__.py +0 -0
- {capycli-2.6.0 → capycli-2.6.0.dev1}/capycli/dependencies/handle_dependencies.py +0 -0
- {capycli-2.6.0 → capycli-2.6.0.dev1}/capycli/dependencies/javascript.py +0 -0
- {capycli-2.6.0 → capycli-2.6.0.dev1}/capycli/dependencies/maven_list.py +0 -0
- {capycli-2.6.0 → capycli-2.6.0.dev1}/capycli/dependencies/maven_pom.py +0 -0
- {capycli-2.6.0 → capycli-2.6.0.dev1}/capycli/dependencies/nuget.py +0 -0
- {capycli-2.6.0 → capycli-2.6.0.dev1}/capycli/main/__init__.py +0 -0
- {capycli-2.6.0 → capycli-2.6.0.dev1}/capycli/main/application.py +0 -0
- {capycli-2.6.0 → capycli-2.6.0.dev1}/capycli/main/argument_parser.py +0 -0
- {capycli-2.6.0 → capycli-2.6.0.dev1}/capycli/main/cli.py +0 -0
- {capycli-2.6.0 → capycli-2.6.0.dev1}/capycli/main/exceptions.py +0 -0
- {capycli-2.6.0 → capycli-2.6.0.dev1}/capycli/main/result_codes.py +0 -0
- {capycli-2.6.0 → capycli-2.6.0.dev1}/capycli/mapping/handle_mapping.py +0 -0
- {capycli-2.6.0 → capycli-2.6.0.dev1}/capycli/mapping/mapping_to_html.py +0 -0
- {capycli-2.6.0 → capycli-2.6.0.dev1}/capycli/mapping/mapping_to_xlsx.py +0 -0
- {capycli-2.6.0 → capycli-2.6.0.dev1}/capycli/moverview/handle_moverview.py +0 -0
- {capycli-2.6.0 → capycli-2.6.0.dev1}/capycli/moverview/moverview_to_html.py +0 -0
- {capycli-2.6.0 → capycli-2.6.0.dev1}/capycli/moverview/moverview_to_xlsx.py +0 -0
- {capycli-2.6.0 → capycli-2.6.0.dev1}/capycli/project/__init__.py +0 -0
- {capycli-2.6.0 → capycli-2.6.0.dev1}/capycli/project/check_prerequisites.py +0 -0
- {capycli-2.6.0 → capycli-2.6.0.dev1}/capycli/project/create_bom.py +0 -0
- {capycli-2.6.0 → capycli-2.6.0.dev1}/capycli/project/create_project.py +0 -0
- {capycli-2.6.0 → capycli-2.6.0.dev1}/capycli/project/create_readme.py +0 -0
- {capycli-2.6.0 → capycli-2.6.0.dev1}/capycli/project/find_project.py +0 -0
- {capycli-2.6.0 → capycli-2.6.0.dev1}/capycli/project/get_license_info.py +0 -0
- {capycli-2.6.0 → capycli-2.6.0.dev1}/capycli/project/handle_project.py +0 -0
- {capycli-2.6.0 → capycli-2.6.0.dev1}/capycli/project/show_ecc.py +0 -0
- {capycli-2.6.0 → capycli-2.6.0.dev1}/capycli/project/show_licenses.py +0 -0
- {capycli-2.6.0 → capycli-2.6.0.dev1}/capycli/project/show_project.py +0 -0
- {capycli-2.6.0 → capycli-2.6.0.dev1}/capycli/project/show_vulnerabilities.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: capycli
|
|
3
|
-
Version: 2.6.0
|
|
3
|
+
Version: 2.6.0.dev1
|
|
4
4
|
Summary: CaPyCli - Clearing Automation Python Command Line Interface for SW360
|
|
5
5
|
Home-page: https://github.com/sw360/capycli
|
|
6
6
|
License: MIT
|
|
@@ -27,7 +27,6 @@ Requires-Dist: colorama (>=0.4.3,<0.5.0)
|
|
|
27
27
|
Requires-Dist: cyclonedx-python-lib (>=8.0.0,<9.0.0)
|
|
28
28
|
Requires-Dist: dateparser (>=1.1.8,<2.0.0)
|
|
29
29
|
Requires-Dist: importlib-resources (>=5.12.0,<6.0.0)
|
|
30
|
-
Requires-Dist: jsonschema (>=4.23.0,<5.0.0)
|
|
31
30
|
Requires-Dist: openpyxl (>=3.0.3,<4.0.0)
|
|
32
31
|
Requires-Dist: packageurl-python (>=0.15.6,<0.16.0)
|
|
33
32
|
Requires-Dist: pyjwt (>=1.7.1,<2.0.0)
|
|
@@ -134,7 +133,7 @@ Commands and Sub-Commands
|
|
|
134
133
|
Prerequisites checks whether all prerequisites for a successful
|
|
135
134
|
software clearing are fulfilled
|
|
136
135
|
Show show project details
|
|
137
|
-
Licenses show licenses of all cleared
|
|
136
|
+
Licenses show licenses of all cleared compponents
|
|
138
137
|
Create create or update a project on SW360
|
|
139
138
|
Update update an exiting project, preserving linked releases
|
|
140
139
|
GetLicenseInfo get license info of all project components
|
|
@@ -185,12 +184,6 @@ Options:
|
|
|
185
184
|
--forceerror FORCE_ERROR force an error exit code in case of visual errors
|
|
186
185
|
```
|
|
187
186
|
|
|
188
|
-
**Note about `--forceexit` and `--forceerror`**:
|
|
189
|
-
These options are not available for all commands. At the moment
|
|
190
|
-
|
|
191
|
-
* `--forceexit` applies only to the `project vulnerabilities` command.
|
|
192
|
-
* `--forceerror` applies only to the `project prerequisites` and `project getlicenseinfo` commands.
|
|
193
|
-
|
|
194
187
|
## Use Cases
|
|
195
188
|
|
|
196
189
|
Over the time we implemented more and more commands with more and more parameters.
|
|
@@ -90,7 +90,7 @@ Commands and Sub-Commands
|
|
|
90
90
|
Prerequisites checks whether all prerequisites for a successful
|
|
91
91
|
software clearing are fulfilled
|
|
92
92
|
Show show project details
|
|
93
|
-
Licenses show licenses of all cleared
|
|
93
|
+
Licenses show licenses of all cleared compponents
|
|
94
94
|
Create create or update a project on SW360
|
|
95
95
|
Update update an exiting project, preserving linked releases
|
|
96
96
|
GetLicenseInfo get license info of all project components
|
|
@@ -141,12 +141,6 @@ Options:
|
|
|
141
141
|
--forceerror FORCE_ERROR force an error exit code in case of visual errors
|
|
142
142
|
```
|
|
143
143
|
|
|
144
|
-
**Note about `--forceexit` and `--forceerror`**:
|
|
145
|
-
These options are not available for all commands. At the moment
|
|
146
|
-
|
|
147
|
-
* `--forceexit` applies only to the `project vulnerabilities` command.
|
|
148
|
-
* `--forceerror` applies only to the `project prerequisites` and `project getlicenseinfo` commands.
|
|
149
|
-
|
|
150
144
|
## Use Cases
|
|
151
145
|
|
|
152
146
|
Over the time we implemented more and more commands with more and more parameters.
|
|
@@ -18,16 +18,17 @@ from sortedcontainers import SortedSet
|
|
|
18
18
|
import capycli.common.json_support
|
|
19
19
|
import capycli.common.script_base
|
|
20
20
|
from capycli import get_logger
|
|
21
|
-
from capycli.bom.csv import CsvSupport
|
|
22
|
-
from capycli.bom.html import HtmlConversionSupport
|
|
23
|
-
from capycli.bom.legacy import LegacySupport
|
|
24
|
-
from capycli.bom.legacy_cx import LegacyCx
|
|
25
|
-
from capycli.bom.plaintext import PlainTextSupport
|
|
26
21
|
from capycli.common.capycli_bom_support import CaPyCliBom
|
|
27
|
-
from capycli.common.print import print_red, print_text
|
|
22
|
+
from capycli.common.print import print_red, print_text
|
|
28
23
|
from capycli.main.exceptions import CaPyCliException
|
|
29
24
|
from capycli.main.result_codes import ResultCode
|
|
30
25
|
|
|
26
|
+
from .csv import CsvSupport
|
|
27
|
+
from .html import HtmlConversionSupport
|
|
28
|
+
from .legacy import LegacySupport
|
|
29
|
+
from .legacy_cx import LegacyCx
|
|
30
|
+
from .plaintext import PlainTextSupport
|
|
31
|
+
|
|
31
32
|
LOG = get_logger(__name__)
|
|
32
33
|
|
|
33
34
|
|
|
@@ -46,8 +47,6 @@ class BomFormat(str, Enum):
|
|
|
46
47
|
LEGACY_CX = "legacy-cx"
|
|
47
48
|
# HTML
|
|
48
49
|
HTML = "html"
|
|
49
|
-
# CycloneDX XML
|
|
50
|
-
XML = "xml"
|
|
51
50
|
|
|
52
51
|
|
|
53
52
|
class BomConvert(capycli.common.script_base.ScriptBase):
|
|
@@ -76,11 +75,6 @@ class BomConvert(capycli.common.script_base.ScriptBase):
|
|
|
76
75
|
cdx_components = sbom.components
|
|
77
76
|
project = sbom.metadata.component
|
|
78
77
|
print_text(f" {len(cdx_components)} components read from file {inputfile}")
|
|
79
|
-
elif (inputformat == BomFormat.XML):
|
|
80
|
-
sbom = CaPyCliBom.read_sbom_xml(inputfile)
|
|
81
|
-
cdx_components = sbom.components
|
|
82
|
-
project = sbom.metadata.component
|
|
83
|
-
print_text(f" {len(cdx_components)} components read from file {inputfile}")
|
|
84
78
|
elif inputformat == BomFormat.LEGACY:
|
|
85
79
|
cdx_components = SortedSet(LegacySupport.legacy_to_cdx_components(inputfile))
|
|
86
80
|
print_text(f" {len(cdx_components)} components read from file {inputfile}")
|
|
@@ -112,12 +106,6 @@ class BomConvert(capycli.common.script_base.ScriptBase):
|
|
|
112
106
|
else:
|
|
113
107
|
CaPyCliBom.write_simple_sbom(cdx_components, outputfile)
|
|
114
108
|
print_text(f" {len(cdx_components)} components written to file {outputfile}")
|
|
115
|
-
elif outputformat == BomFormat.XML:
|
|
116
|
-
if sbom:
|
|
117
|
-
CaPyCliBom.write_sbom_xml(sbom, outputfile)
|
|
118
|
-
print_text(f" {len(sbom.components)} components written to file {outputfile}")
|
|
119
|
-
else:
|
|
120
|
-
print_yellow(" This command only works for CycloneDX SBOM input files!")
|
|
121
109
|
elif outputformat == BomFormat.LEGACY:
|
|
122
110
|
LegacySupport.write_cdx_components_as_legacy(cdx_components, outputfile)
|
|
123
111
|
print_text(f" {len(cdx_components)} components written to file {outputfile}")
|
|
@@ -151,15 +139,15 @@ class BomConvert(capycli.common.script_base.ScriptBase):
|
|
|
151
139
|
|
|
152
140
|
def display_help(self) -> None:
|
|
153
141
|
"""Display (local) help."""
|
|
154
|
-
print("usage: CaPyCli bom convert [-h] [-i INPUTFILE] [-if {capycli,
|
|
155
|
-
print(" [-o OUTPUTFILE] [-of {capycli,text,csv,legacy,legacy-cx,html
|
|
142
|
+
print("usage: CaPyCli bom convert [-h] [-i INPUTFILE] [-if {capycli,text,csv,legacy,legacy-cx}]")
|
|
143
|
+
print(" [-o OUTPUTFILE] [-of {capycli,text,csv,legacy,legacy-cx,html}]")
|
|
156
144
|
print("")
|
|
157
145
|
print("optional arguments:")
|
|
158
146
|
print(" -h, --help Show this help message and exit")
|
|
159
147
|
print(" -i INPUTFILE Input BOM filename (JSON)")
|
|
160
148
|
print(" -o OUTPUTFILE Output BOM filename")
|
|
161
|
-
print(" -if INPUTFORMAT
|
|
162
|
-
print(" -of OUTPUTFORMAT
|
|
149
|
+
print(" -if INPUTFORMAT Specify input file format: capycli|sbom|text|csv|legacy|legacy-cx")
|
|
150
|
+
print(" -of OUTPUTFORMAT Specify output file format: capycli|text|csv|legacy|html")
|
|
163
151
|
|
|
164
152
|
def run(self, args: Any) -> None:
|
|
165
153
|
"""Main method()"""
|
|
@@ -299,6 +299,8 @@ class BomCreateComponents(capycli.common.script_base.ScriptBase):
|
|
|
299
299
|
print_yellow(
|
|
300
300
|
" WARNING: SW360 source URL", release_data["sourceCodeDownloadurl"],
|
|
301
301
|
"differs from BOM URL", data["sourceCodeDownloadurl"])
|
|
302
|
+
if data["sourceCodeDownloadurl"].endswith(('zip', 'tgz', 'tar.gz', 'tar')):
|
|
303
|
+
update_data["sourceCodeDownloadurl"] = data["sourceCodeDownloadurl"]
|
|
302
304
|
|
|
303
305
|
if "binaryDownloadurl" in data and data["binaryDownloadurl"]:
|
|
304
306
|
if not release_data.get("binaryDownloadurl", ""):
|
|
@@ -366,6 +368,10 @@ class BomCreateComponents(capycli.common.script_base.ScriptBase):
|
|
|
366
368
|
filename = str(CycloneDxSupport.get_ext_ref_binary_file(cx_comp))
|
|
367
369
|
filehash = str(CycloneDxSupport.get_binary_file_hash(cx_comp))
|
|
368
370
|
|
|
371
|
+
if filename is not None and filename.endswith('.git'):
|
|
372
|
+
print_red(" WARNING: resetting filename to prevent uploading .git file")
|
|
373
|
+
filename = None
|
|
374
|
+
|
|
369
375
|
# Note that we retrieve the SHA1 has from the CycloneDX data.
|
|
370
376
|
# But there is no guarantee that this *IS* really a SHA1 hash!
|
|
371
377
|
|
|
@@ -374,10 +380,6 @@ class BomCreateComponents(capycli.common.script_base.ScriptBase):
|
|
|
374
380
|
if filename_parsed:
|
|
375
381
|
filename = os.path.basename(filename_parsed.path)
|
|
376
382
|
|
|
377
|
-
if filetype in ["SOURCE", "SOURCE_SELF"] and filename is not None and filename.endswith('.git'):
|
|
378
|
-
print_red(" WARNING: resetting filename to prevent uploading .git file")
|
|
379
|
-
filename = None
|
|
380
|
-
|
|
381
383
|
if not filename:
|
|
382
384
|
print_red(" Unable to identify filename from url!")
|
|
383
385
|
return
|
|
@@ -20,7 +20,7 @@ import capycli.common.json_support
|
|
|
20
20
|
import capycli.common.script_base
|
|
21
21
|
from capycli import get_logger
|
|
22
22
|
from capycli.bom.legacy import LegacySupport
|
|
23
|
-
from capycli.common.capycli_bom_support import CaPyCliBom, CycloneDxSupport,
|
|
23
|
+
from capycli.common.capycli_bom_support import CaPyCliBom, CycloneDxSupport, SbomWriter
|
|
24
24
|
from capycli.common.print import print_red, print_text, print_yellow
|
|
25
25
|
from capycli.main.result_codes import ResultCode
|
|
26
26
|
|
|
@@ -320,7 +320,6 @@ class FilterBom(capycli.common.script_base.ScriptBase):
|
|
|
320
320
|
|
|
321
321
|
print_text("Writing new SBOM to " + args.outputfile)
|
|
322
322
|
try:
|
|
323
|
-
SbomCreator.add_standard_bom_standard(sbom)
|
|
324
323
|
SbomWriter.write_to_json(sbom, args.outputfile, True)
|
|
325
324
|
except Exception as ex:
|
|
326
325
|
print_red("Error writing updated SBOM file: " + repr(ex))
|
|
@@ -524,7 +524,7 @@ class FindSources(capycli.common.script_base.ScriptBase):
|
|
|
524
524
|
if self.verbose:
|
|
525
525
|
print(" No Source code URL available - try to find with language:")
|
|
526
526
|
source_url = self.find_source_url_by_language(component)
|
|
527
|
-
if not source_url and
|
|
527
|
+
if not source_url and language.lower() == "golang":
|
|
528
528
|
if self.verbose:
|
|
529
529
|
print(" No Source code URL available - try to find on pkg.go.dev:")
|
|
530
530
|
source_url = self.find_golang_url(component)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# -------------------------------------------------------------------------------
|
|
2
|
-
# Copyright (c) 2019-
|
|
2
|
+
# Copyright (c) 2019-23 Siemens
|
|
3
3
|
# All Rights Reserved.
|
|
4
4
|
# Author: thomas.graf@siemens.com
|
|
5
5
|
#
|
|
@@ -10,7 +10,6 @@ import sys
|
|
|
10
10
|
from typing import Any
|
|
11
11
|
|
|
12
12
|
import capycli.bom.bom_convert
|
|
13
|
-
import capycli.bom.bom_validate
|
|
14
13
|
import capycli.bom.check_bom
|
|
15
14
|
import capycli.bom.check_bom_item_status
|
|
16
15
|
import capycli.bom.check_granularity
|
|
@@ -47,10 +46,9 @@ def run_bom_command(args: Any) -> None:
|
|
|
47
46
|
print(" CreateComponents create new components and releases on SW360 (use with care!)")
|
|
48
47
|
print(" DownloadSources download source files from the URL specified in the SBOM")
|
|
49
48
|
print(" Granularity check a bill of material for potential component granularity issues")
|
|
50
|
-
print(" Diff compare two bills of material")
|
|
51
|
-
print(" Merge merge two bills of material")
|
|
52
|
-
print(" Findsources determine the source code for SBOM items")
|
|
53
|
-
print(" Validate validate an SBOM")
|
|
49
|
+
print(" Diff compare two bills of material.")
|
|
50
|
+
print(" Merge merge two bills of material.")
|
|
51
|
+
print(" Findsources determine the source code for SBOM items.")
|
|
54
52
|
return
|
|
55
53
|
|
|
56
54
|
subcommand = args.command[1].lower()
|
|
@@ -133,11 +131,5 @@ def run_bom_command(args: Any) -> None:
|
|
|
133
131
|
app13.run(args)
|
|
134
132
|
return
|
|
135
133
|
|
|
136
|
-
if subcommand == "validate":
|
|
137
|
-
"""Validate an SBOM."""
|
|
138
|
-
app14 = capycli.bom.bom_validate.BomValidate()
|
|
139
|
-
app14.run(args)
|
|
140
|
-
return
|
|
141
|
-
|
|
142
134
|
print_red("Unknown sub-command: ")
|
|
143
135
|
sys.exit(ResultCode.RESULT_COMMAND_ERROR)
|
|
@@ -11,9 +11,7 @@ import sys
|
|
|
11
11
|
from typing import Any, Optional
|
|
12
12
|
|
|
13
13
|
from cyclonedx.model.bom import Bom
|
|
14
|
-
from cyclonedx.model.bom_ref import BomRef
|
|
15
14
|
from cyclonedx.model.component import Component
|
|
16
|
-
from cyclonedx.model.dependency import Dependency
|
|
17
15
|
|
|
18
16
|
import capycli.common.script_base
|
|
19
17
|
from capycli.common.capycli_bom_support import CaPyCliBom, SbomWriter
|
|
@@ -83,36 +81,12 @@ class MergeBom(capycli.common.script_base.ScriptBase):
|
|
|
83
81
|
|
|
84
82
|
return None
|
|
85
83
|
|
|
86
|
-
def find_dependency(self, bom_ref: BomRef, bom: Bom) -> Optional[Component]:
|
|
87
|
-
"""Find a certain dependency (component) by bom_ref in the given bom."""
|
|
88
|
-
component: Component
|
|
89
|
-
for component in bom.components:
|
|
90
|
-
if component.bom_ref == bom_ref:
|
|
91
|
-
return component
|
|
92
|
-
|
|
93
|
-
return None
|
|
94
|
-
|
|
95
84
|
def merge_boms(self, bom_old: Bom, bom_new: Bom) -> Bom:
|
|
96
|
-
"""Merges two SBOMs."""
|
|
97
|
-
|
|
98
|
-
# step 1: merge components
|
|
99
|
-
component_new: Component
|
|
100
85
|
for component_new in bom_new.components:
|
|
101
86
|
found = self.find_in_bom(bom_old, component_new)
|
|
102
87
|
if not found:
|
|
103
88
|
bom_old.components.add(component_new)
|
|
104
89
|
|
|
105
|
-
# step 2: reconstruct dependencies
|
|
106
|
-
dep: Dependency
|
|
107
|
-
for dep in bom_new.dependencies:
|
|
108
|
-
cr = self.find_dependency(dep.ref, bom_new)
|
|
109
|
-
if not cr:
|
|
110
|
-
continue
|
|
111
|
-
for d in dep.dependencies:
|
|
112
|
-
cd = self.find_dependency(d.ref, bom_new)
|
|
113
|
-
if cd:
|
|
114
|
-
bom_old.register_dependency(cr, [cd])
|
|
115
|
-
|
|
116
90
|
return bom_old
|
|
117
91
|
|
|
118
92
|
def run(self, args: Any) -> None:
|
|
@@ -10,9 +10,8 @@ import json
|
|
|
10
10
|
import os
|
|
11
11
|
import pathlib
|
|
12
12
|
from enum import Enum
|
|
13
|
-
from typing import
|
|
13
|
+
from typing import Any, List, Optional, Union
|
|
14
14
|
|
|
15
|
-
from cyclonedx.exception import MissingOptionalDependencyException
|
|
16
15
|
from cyclonedx.factory.license import LicenseFactory
|
|
17
16
|
from cyclonedx.model import ExternalReference, ExternalReferenceType, HashAlgorithm, HashType, Property, XsUri
|
|
18
17
|
from cyclonedx.model.bom import Bom
|
|
@@ -20,22 +19,12 @@ from cyclonedx.model.component import Component, ComponentType
|
|
|
20
19
|
from cyclonedx.model.contact import OrganizationalEntity
|
|
21
20
|
from cyclonedx.model.definition import Definitions, Standard
|
|
22
21
|
from cyclonedx.model.tool import ToolRepository
|
|
23
|
-
from cyclonedx.output import make_outputter
|
|
24
22
|
from cyclonedx.output.json import JsonV1Dot6
|
|
25
|
-
from cyclonedx.schema import OutputFormat, SchemaVersion
|
|
26
|
-
from cyclonedx.validation.json import JsonStrictValidator
|
|
27
|
-
|
|
28
|
-
if TYPE_CHECKING:
|
|
29
|
-
from cyclonedx.output.json import Json as JsonOutputter
|
|
30
|
-
from cyclonedx.output.xml import Xml as XmlOutputter
|
|
31
|
-
|
|
32
|
-
from defusedxml import ElementTree as SafeElementTree # type:ignore[import-untyped]
|
|
33
23
|
from sortedcontainers import SortedSet
|
|
34
24
|
|
|
35
25
|
import capycli.common.script_base
|
|
36
26
|
from capycli import LOG
|
|
37
27
|
from capycli.common import json_support
|
|
38
|
-
from capycli.common.print import print_green, print_yellow
|
|
39
28
|
from capycli.main.exceptions import CaPyCliException
|
|
40
29
|
|
|
41
30
|
# -------------------------------------
|
|
@@ -200,10 +189,6 @@ class CycloneDxSupport():
|
|
|
200
189
|
and (ext_ref.comment == CaPyCliBom.SOURCE_URL_COMMENT):
|
|
201
190
|
return ext_ref.url
|
|
202
191
|
|
|
203
|
-
# new for CyCloneDX 1.6
|
|
204
|
-
if (ext_ref.type == ExternalReferenceType.SOURCE_DISTRIBUTION):
|
|
205
|
-
return ext_ref.url
|
|
206
|
-
|
|
207
192
|
return ""
|
|
208
193
|
|
|
209
194
|
@staticmethod
|
|
@@ -260,33 +245,6 @@ class SbomCreator():
|
|
|
260
245
|
def __init__(self) -> None:
|
|
261
246
|
pass
|
|
262
247
|
|
|
263
|
-
@staticmethod
|
|
264
|
-
def remove_all_tools(sbom: Bom) -> None:
|
|
265
|
-
"""Remove all existing tool entries."""
|
|
266
|
-
if (not sbom) or (not sbom.metadata) or (not sbom.metadata.tools):
|
|
267
|
-
return
|
|
268
|
-
|
|
269
|
-
if not sbom.metadata.tools.components:
|
|
270
|
-
return
|
|
271
|
-
|
|
272
|
-
sbom.metadata.tools.components.clear()
|
|
273
|
-
|
|
274
|
-
@staticmethod
|
|
275
|
-
def has_capycli_tool(sbom: Bom) -> bool:
|
|
276
|
-
"""Checks whether CaPyCLI is already set as tool."""
|
|
277
|
-
if (not sbom) or (not sbom.metadata) or (not sbom.metadata.tools):
|
|
278
|
-
return False
|
|
279
|
-
|
|
280
|
-
if not sbom.metadata.tools.components:
|
|
281
|
-
return False
|
|
282
|
-
|
|
283
|
-
comp: Component
|
|
284
|
-
for comp in sbom.metadata.tools.components:
|
|
285
|
-
if comp.name == "CaPyCLI":
|
|
286
|
-
return True
|
|
287
|
-
|
|
288
|
-
return False
|
|
289
|
-
|
|
290
248
|
@staticmethod
|
|
291
249
|
def get_capycli_tool(version: str = "") -> Component:
|
|
292
250
|
"""Get CaPyCLI as tool."""
|
|
@@ -428,12 +386,11 @@ class SbomWriter():
|
|
|
428
386
|
|
|
429
387
|
@classmethod
|
|
430
388
|
def write_to_json(cls, sbom: Bom, outputfile: str, pretty_print: bool = False) -> None:
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
if not SbomCreator.has_capycli_tool(sbom):
|
|
389
|
+
SbomWriter._remove_tool_python_lib(sbom)
|
|
390
|
+
if len(sbom.metadata.tools) == 0:
|
|
434
391
|
sbom.metadata.tools.components.add(SbomCreator.get_capycli_tool())
|
|
435
392
|
|
|
436
|
-
writer
|
|
393
|
+
writer = JsonV1Dot6(sbom)
|
|
437
394
|
cls.remove_empty_properties_in_sbom(sbom)
|
|
438
395
|
|
|
439
396
|
if pretty_print:
|
|
@@ -442,20 +399,6 @@ class SbomWriter():
|
|
|
442
399
|
else:
|
|
443
400
|
writer.output_to_file(filename=outputfile, allow_overwrite=True)
|
|
444
401
|
|
|
445
|
-
@classmethod
|
|
446
|
-
def write_to_xml(cls, sbom: Bom, outputfile: str, pretty_print: bool = False) -> None:
|
|
447
|
-
"""Write CaPyCLI/CycloneDX XML."""
|
|
448
|
-
SbomCreator.remove_all_tools(sbom)
|
|
449
|
-
if not SbomCreator.has_capycli_tool(sbom):
|
|
450
|
-
sbom.metadata.tools.components.add(SbomCreator.get_capycli_tool())
|
|
451
|
-
cls.remove_empty_properties_in_sbom(sbom)
|
|
452
|
-
|
|
453
|
-
writer: 'XmlOutputter' = make_outputter(sbom, OutputFormat.XML, SchemaVersion.V1_6)
|
|
454
|
-
if pretty_print:
|
|
455
|
-
writer.output_to_file(outputfile, indent=2)
|
|
456
|
-
else:
|
|
457
|
-
writer.output_to_file(outputfile)
|
|
458
|
-
|
|
459
402
|
|
|
460
403
|
class CaPyCliBom():
|
|
461
404
|
"""
|
|
@@ -477,23 +420,19 @@ class CaPyCliBom():
|
|
|
477
420
|
except Exception as exp:
|
|
478
421
|
raise CaPyCliException("Error reading raw JSON file: " + str(exp))
|
|
479
422
|
|
|
423
|
+
# my_json_validator = JsonStrictValidator(SchemaVersion.V1_6)
|
|
424
|
+
# try:
|
|
425
|
+
# validation_errors = my_json_validator.validate_str(json_string)
|
|
426
|
+
# if validation_errors:
|
|
427
|
+
# raise CaPyCliException("JSON validation error: " + repr(validation_errors))
|
|
428
|
+
#
|
|
429
|
+
# print_green("JSON file successfully validated")
|
|
430
|
+
# except MissingOptionalDependencyException as error:
|
|
431
|
+
# print_yellow('JSON-validation was skipped due to', error)
|
|
480
432
|
bom = Bom.from_json( # type: ignore[attr-defined]
|
|
481
433
|
json_data)
|
|
482
434
|
return bom
|
|
483
435
|
|
|
484
|
-
@classmethod
|
|
485
|
-
def read_sbom_xml(cls, inputfile: str) -> Bom:
|
|
486
|
-
LOG.debug(f"Reading from file {inputfile}")
|
|
487
|
-
with open(inputfile) as fin:
|
|
488
|
-
try:
|
|
489
|
-
xml_data = fin.read()
|
|
490
|
-
except Exception as exp:
|
|
491
|
-
raise CaPyCliException("Error reading raw XML file: " + str(exp))
|
|
492
|
-
|
|
493
|
-
bom = Bom.from_xml( # type: ignore[attr-defined]
|
|
494
|
-
SafeElementTree.fromstring(xml_data))
|
|
495
|
-
return bom
|
|
496
|
-
|
|
497
436
|
@classmethod
|
|
498
437
|
def write_sbom(cls, sbom: Bom, outputfile: str) -> None:
|
|
499
438
|
LOG.debug(f"Writing to file {outputfile}")
|
|
@@ -505,22 +444,6 @@ class CaPyCliBom():
|
|
|
505
444
|
raise CaPyCliException("Error writing CaPyCLI file: " + str(exp))
|
|
506
445
|
LOG.debug("done")
|
|
507
446
|
|
|
508
|
-
@classmethod
|
|
509
|
-
def write_sbom_xml(cls, sbom: Bom, outputfile: str) -> None:
|
|
510
|
-
LOG.debug(f"Writing to file {outputfile}")
|
|
511
|
-
try:
|
|
512
|
-
# always add/update profile
|
|
513
|
-
SbomCreator.add_profile(sbom, "clearing")
|
|
514
|
-
|
|
515
|
-
# ensure that file does exist
|
|
516
|
-
if os.path.isfile(outputfile):
|
|
517
|
-
os.remove(outputfile)
|
|
518
|
-
|
|
519
|
-
SbomWriter.write_to_xml(sbom, outputfile, pretty_print=True)
|
|
520
|
-
except Exception as exp:
|
|
521
|
-
raise CaPyCliException("Error writing CaPyCLI file: " + str(exp))
|
|
522
|
-
LOG.debug("done")
|
|
523
|
-
|
|
524
447
|
@classmethod
|
|
525
448
|
def write_simple_sbom(cls, bom: SortedSet, outputfile: str) -> None:
|
|
526
449
|
LOG.debug(f"Writing to file {outputfile}")
|
|
@@ -531,39 +454,3 @@ class CaPyCliBom():
|
|
|
531
454
|
except Exception as exp:
|
|
532
455
|
raise CaPyCliException("Error writing CaPyCLI file: " + str(exp))
|
|
533
456
|
LOG.debug("done")
|
|
534
|
-
|
|
535
|
-
@classmethod
|
|
536
|
-
def _string_to_schema_version(cls, spec_version: str) -> SchemaVersion:
|
|
537
|
-
"""Convert the given string to a CycloneDX spec version."""
|
|
538
|
-
if spec_version == "1.6":
|
|
539
|
-
return SchemaVersion.V1_6
|
|
540
|
-
if spec_version == "1.5":
|
|
541
|
-
return SchemaVersion.V1_5
|
|
542
|
-
if spec_version == "1.4":
|
|
543
|
-
return SchemaVersion.V1_4
|
|
544
|
-
|
|
545
|
-
print_yellow("Unknown CycloneDX spec version, defaulting to 1.6")
|
|
546
|
-
return SchemaVersion.V1_6
|
|
547
|
-
|
|
548
|
-
@classmethod
|
|
549
|
-
def validate_sbom(cls, inputfile: str, spec_version: str) -> bool:
|
|
550
|
-
"""Validate the given SBOM file against the given CycloneDX spec. version."""
|
|
551
|
-
LOG.debug(f"Validating SBOM from file {inputfile}")
|
|
552
|
-
with open(inputfile) as fin:
|
|
553
|
-
try:
|
|
554
|
-
json_string = fin.read()
|
|
555
|
-
except Exception as exp:
|
|
556
|
-
raise CaPyCliException("Error reading raw JSON file: " + str(exp))
|
|
557
|
-
|
|
558
|
-
my_json_validator = JsonStrictValidator(cls._string_to_schema_version(spec_version))
|
|
559
|
-
try:
|
|
560
|
-
validation_errors = my_json_validator.validate_str(json_string)
|
|
561
|
-
if validation_errors:
|
|
562
|
-
raise CaPyCliException("JSON validation error: " + repr(validation_errors))
|
|
563
|
-
|
|
564
|
-
print_green("JSON file successfully validated.")
|
|
565
|
-
return True
|
|
566
|
-
except MissingOptionalDependencyException as error:
|
|
567
|
-
print_yellow('JSON-validation was skipped due to', error)
|
|
568
|
-
|
|
569
|
-
return False
|
|
@@ -310,7 +310,7 @@ class GetPythonDependencies(capycli.common.script_base.ScriptBase):
|
|
|
310
310
|
|
|
311
311
|
print()
|
|
312
312
|
|
|
313
|
-
def determine_file_type(self,
|
|
313
|
+
def determine_file_type(self, filename: str) -> InputFileType:
|
|
314
314
|
"""
|
|
315
315
|
Try to guess the input file type from the filename.
|
|
316
316
|
|
|
@@ -320,14 +320,13 @@ class GetPythonDependencies(capycli.common.script_base.ScriptBase):
|
|
|
320
320
|
Returns:
|
|
321
321
|
An InputFileType value.
|
|
322
322
|
"""
|
|
323
|
-
filename = os.path.basename(
|
|
323
|
+
filename = os.path.basename(filename).lower()
|
|
324
324
|
if filename == "requirements.txt":
|
|
325
325
|
LOG.debug("Guessing requirements file")
|
|
326
326
|
return InputFileType.REQUIREMENTS
|
|
327
327
|
|
|
328
|
-
if
|
|
329
|
-
|
|
330
|
-
data = self.read_poetry_lock_file(full_filename)
|
|
328
|
+
if filename == "poetry.lock":
|
|
329
|
+
data = self.read_poetry_lock_file(filename)
|
|
331
330
|
if data:
|
|
332
331
|
LOG.debug("Guessing poetry.lock file")
|
|
333
332
|
return InputFileType.POETRY_LOCK
|
|
@@ -46,10 +46,9 @@ class CommandlineSupport():
|
|
|
46
46
|
CreateComponents create new components and releases on SW360 (use with care!)
|
|
47
47
|
DownloadSources download source files from the URL specified in the SBOM
|
|
48
48
|
Granularity check a bill of material for potential component granularity issues
|
|
49
|
-
Diff compare two bills of material
|
|
50
|
-
Merge merge two bills of material
|
|
51
|
-
Findsources determine the source code for SBOM items
|
|
52
|
-
Validate validate an SBOM
|
|
49
|
+
Diff compare two bills of material.
|
|
50
|
+
Merge merge two bills of material.
|
|
51
|
+
Findsources determine the source code for SBOM items.
|
|
53
52
|
|
|
54
53
|
mapping
|
|
55
54
|
ToHtml create a HTML page showing the mapping result
|
|
@@ -64,7 +63,7 @@ class CommandlineSupport():
|
|
|
64
63
|
Prerequisites checks whether all prerequisites for a successful
|
|
65
64
|
software clearing are fulfilled
|
|
66
65
|
Show show project details
|
|
67
|
-
Licenses show licenses of all cleared
|
|
66
|
+
Licenses show licenses of all cleared compponents
|
|
68
67
|
Create create or update a project on SW360
|
|
69
68
|
Update update an exiting project, preserving linked releases
|
|
70
69
|
GetLicenseInfo get license info of all project components
|
|
@@ -107,7 +106,6 @@ class CommandlineSupport():
|
|
|
107
106
|
input_formats.append(BomFormat.LEGACY_CX)
|
|
108
107
|
input_formats.append(BomFormat.SBOM)
|
|
109
108
|
input_formats.append(BomFormat.CAPYCLI)
|
|
110
|
-
input_formats.append(BomFormat.XML)
|
|
111
109
|
|
|
112
110
|
output_formats = []
|
|
113
111
|
output_formats.append(BomFormat.CAPYCLI)
|
|
@@ -116,7 +114,6 @@ class CommandlineSupport():
|
|
|
116
114
|
output_formats.append(BomFormat.CSV)
|
|
117
115
|
output_formats.append(BomFormat.LEGACY)
|
|
118
116
|
output_formats.append(BomFormat.HTML)
|
|
119
|
-
output_formats.append(BomFormat.XML)
|
|
120
117
|
|
|
121
118
|
map_modes = []
|
|
122
119
|
map_modes.append(MapMode.ALL)
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
[tool.poetry]
|
|
5
5
|
name = "capycli"
|
|
6
|
-
version = "2.6.0"
|
|
6
|
+
version = "2.6.0.dev1"
|
|
7
7
|
description = "CaPyCli - Clearing Automation Python Command Line Interface for SW360"
|
|
8
8
|
authors = ["Thomas Graf <thomas.graf@siemens.com>"]
|
|
9
9
|
license = "MIT"
|
|
@@ -52,7 +52,6 @@ dateparser = "^1.1.8"
|
|
|
52
52
|
urllib3 = "*"
|
|
53
53
|
importlib-resources = "^5.12.0"
|
|
54
54
|
beautifulsoup4 = "^4.11.1"
|
|
55
|
-
jsonschema = "^4.23.0"
|
|
56
55
|
|
|
57
56
|
[tool.poetry.group.dev.dependencies]
|
|
58
57
|
flake8 = ">=3.7.8"
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
# -------------------------------------------------------------------------------
|
|
2
|
-
# Copyright (c) 2024 Siemens
|
|
3
|
-
# All Rights Reserved.
|
|
4
|
-
# Author: thomas.graf@siemens.com
|
|
5
|
-
#
|
|
6
|
-
# SPDX-License-Identifier: MIT
|
|
7
|
-
# -------------------------------------------------------------------------------
|
|
8
|
-
|
|
9
|
-
import os
|
|
10
|
-
import sys
|
|
11
|
-
from typing import Any
|
|
12
|
-
|
|
13
|
-
import capycli.common.json_support
|
|
14
|
-
import capycli.common.script_base
|
|
15
|
-
from capycli import get_logger
|
|
16
|
-
from capycli.common.capycli_bom_support import CaPyCliBom
|
|
17
|
-
from capycli.common.print import print_text
|
|
18
|
-
from capycli.main.exceptions import CaPyCliException
|
|
19
|
-
from capycli.main.result_codes import ResultCode
|
|
20
|
-
|
|
21
|
-
LOG = get_logger(__name__)
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
class BomValidate(capycli.common.script_base.ScriptBase):
|
|
25
|
-
def validate(self, inputfile: str, spec_version: str) -> None:
|
|
26
|
-
"""Main validation method."""
|
|
27
|
-
try:
|
|
28
|
-
if not spec_version:
|
|
29
|
-
print_text("No CycloneDX spec version specified, defaulting to 1.6")
|
|
30
|
-
spec_version = "1.6"
|
|
31
|
-
CaPyCliBom.validate_sbom(inputfile, spec_version)
|
|
32
|
-
except CaPyCliException as error:
|
|
33
|
-
LOG.error(f"Error processing input file: {str(error)}")
|
|
34
|
-
sys.exit(ResultCode.RESULT_GENERAL_ERROR)
|
|
35
|
-
|
|
36
|
-
def check_arguments(self, args: Any) -> None:
|
|
37
|
-
"""Check input arguments."""
|
|
38
|
-
if not args.inputfile:
|
|
39
|
-
LOG.error("No input file specified!")
|
|
40
|
-
sys.exit(ResultCode.RESULT_COMMAND_ERROR)
|
|
41
|
-
|
|
42
|
-
if not os.path.isfile(args.inputfile):
|
|
43
|
-
LOG.error("Input file not found!")
|
|
44
|
-
sys.exit(ResultCode.RESULT_FILE_NOT_FOUND)
|
|
45
|
-
|
|
46
|
-
def display_help(self) -> None:
|
|
47
|
-
"""Display (local) help."""
|
|
48
|
-
print("usage: CaPyCli bom validate [-h] -i INPUTFILE [-version SpecVersion]")
|
|
49
|
-
print("")
|
|
50
|
-
print("optional arguments:")
|
|
51
|
-
print(" -h, --help Show this help message and exit")
|
|
52
|
-
print(" -i INPUTFILE Input BOM filename (JSON)")
|
|
53
|
-
print(" -version SpecVersion CycloneDX spec version to validate against: allowed are 1.4, 1.5, and 1.6")
|
|
54
|
-
|
|
55
|
-
def run(self, args: Any) -> None:
|
|
56
|
-
"""Main method()"""
|
|
57
|
-
print("\n" + capycli.APP_NAME + ", " + capycli.get_app_version() + " - Validate a CaPyCLI/CycloneDX SBOM\n")
|
|
58
|
-
|
|
59
|
-
if args.help:
|
|
60
|
-
self.display_help()
|
|
61
|
-
return
|
|
62
|
-
|
|
63
|
-
self.check_arguments(args)
|
|
64
|
-
if args.debug:
|
|
65
|
-
global LOG
|
|
66
|
-
LOG = get_logger(__name__)
|
|
67
|
-
|
|
68
|
-
self.validate(args.inputfile, args.version)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|