capycli 2.0.0.dev8__py3-none-any.whl
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.
- License.md +27 -0
- capycli/__init__.py +214 -0
- capycli/__main__.py +13 -0
- capycli/bom/__init__.py +10 -0
- capycli/bom/bom_convert.py +163 -0
- capycli/bom/check_bom.py +187 -0
- capycli/bom/check_bom_item_status.py +197 -0
- capycli/bom/check_granularity.py +244 -0
- capycli/bom/create_components.py +644 -0
- capycli/bom/csv.py +69 -0
- capycli/bom/diff_bom.py +279 -0
- capycli/bom/download_sources.py +227 -0
- capycli/bom/filter_bom.py +323 -0
- capycli/bom/findsources.py +278 -0
- capycli/bom/handle_bom.py +134 -0
- capycli/bom/html.py +67 -0
- capycli/bom/legacy.py +312 -0
- capycli/bom/legacy_cx.py +151 -0
- capycli/bom/map_bom.py +1039 -0
- capycli/bom/merge_bom.py +155 -0
- capycli/bom/plaintext.py +69 -0
- capycli/bom/show_bom.py +77 -0
- capycli/common/__init__.py +9 -0
- capycli/common/capycli_bom_support.py +629 -0
- capycli/common/comparable_version.py +161 -0
- capycli/common/component_cache.py +240 -0
- capycli/common/dependencies_base.py +48 -0
- capycli/common/file_support.py +28 -0
- capycli/common/html_support.py +119 -0
- capycli/common/json_support.py +36 -0
- capycli/common/map_result.py +116 -0
- capycli/common/print.py +55 -0
- capycli/common/purl_service.py +169 -0
- capycli/common/purl_store.py +100 -0
- capycli/common/purl_utils.py +85 -0
- capycli/common/script_base.py +165 -0
- capycli/common/script_support.py +78 -0
- capycli/data/__init__.py +9 -0
- capycli/data/granularity_list.csv +1338 -0
- capycli/dependencies/__init__.py +9 -0
- capycli/dependencies/handle_dependencies.py +70 -0
- capycli/dependencies/javascript.py +261 -0
- capycli/dependencies/maven_list.py +333 -0
- capycli/dependencies/maven_pom.py +150 -0
- capycli/dependencies/nuget.py +184 -0
- capycli/dependencies/python.py +345 -0
- capycli/main/__init__.py +9 -0
- capycli/main/application.py +165 -0
- capycli/main/argument_parser.py +101 -0
- capycli/main/cli.py +28 -0
- capycli/main/exceptions.py +14 -0
- capycli/main/options.py +424 -0
- capycli/main/result_codes.py +41 -0
- capycli/mapping/handle_mapping.py +46 -0
- capycli/mapping/mapping_to_html.py +182 -0
- capycli/mapping/mapping_to_xlsx.py +197 -0
- capycli/moverview/handle_moverview.py +46 -0
- capycli/moverview/moverview_to_html.py +122 -0
- capycli/moverview/moverview_to_xlsx.py +170 -0
- capycli/project/__init__.py +9 -0
- capycli/project/check_prerequisites.py +304 -0
- capycli/project/create_bom.py +190 -0
- capycli/project/create_project.py +335 -0
- capycli/project/create_readme.py +546 -0
- capycli/project/find_project.py +128 -0
- capycli/project/get_license_info.py +246 -0
- capycli/project/handle_project.py +118 -0
- capycli/project/show_ecc.py +200 -0
- capycli/project/show_licenses.py +211 -0
- capycli/project/show_project.py +215 -0
- capycli/project/show_vulnerabilities.py +238 -0
- capycli-2.0.0.dev8.dist-info/LICENSES/CC0-1.0.txt +121 -0
- capycli-2.0.0.dev8.dist-info/LICENSES/MIT.txt +27 -0
- capycli-2.0.0.dev8.dist-info/License.md +27 -0
- capycli-2.0.0.dev8.dist-info/METADATA +268 -0
- capycli-2.0.0.dev8.dist-info/RECORD +78 -0
- capycli-2.0.0.dev8.dist-info/WHEEL +4 -0
- capycli-2.0.0.dev8.dist-info/entry_points.txt +3 -0
capycli/bom/merge_bom.py
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# -------------------------------------------------------------------------------
|
|
2
|
+
# Copyright (c) 2023 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
|
+
|
|
12
|
+
from cyclonedx.model.bom import Bom
|
|
13
|
+
from cyclonedx.model.component import Component
|
|
14
|
+
|
|
15
|
+
import capycli.common.script_base
|
|
16
|
+
from capycli.common.capycli_bom_support import CaPyCliBom, SbomWriter
|
|
17
|
+
from capycli.common.print import print_red, print_text
|
|
18
|
+
from capycli.main.result_codes import ResultCode
|
|
19
|
+
|
|
20
|
+
LOG = capycli.get_logger(__name__)
|
|
21
|
+
|
|
22
|
+
"""
|
|
23
|
+
A general question for merge and diff operation is HOW we compare SBOM component.
|
|
24
|
+
For CaPyCLI 1.x the answer was easy: we had a simple JSON format to store component
|
|
25
|
+
information and we only compared name and version.
|
|
26
|
+
|
|
27
|
+
For CaPyCLI 2.x we have CycloneDX as format to store component information and we have
|
|
28
|
+
more questions regarding the component comparison:
|
|
29
|
+
- what about the package-url?
|
|
30
|
+
- what about the bom-ref?
|
|
31
|
+
- what happens if certain properties are different?
|
|
32
|
+
- what happens if certain external references are different?
|
|
33
|
+
- what about the SBOM meta-data
|
|
34
|
+
- what about differences in tools?
|
|
35
|
+
- what about differences in properties like siemens:profile?
|
|
36
|
+
- what about differences in licenses?
|
|
37
|
+
|
|
38
|
+
cyclonedx-cli only compares group, name and version when doing a diff.
|
|
39
|
+
At the moment (version 0.24.2) they do not do any comparison while merging.
|
|
40
|
+
|
|
41
|
+
Current idea:
|
|
42
|
+
* Only compare group, name and version for every component.
|
|
43
|
+
* Do not care about different properties, external references, etc. of components with identical group,
|
|
44
|
+
name and version. We cannot really decide what to do. Merging of these component properties could be
|
|
45
|
+
as wrong as comparing them.
|
|
46
|
+
* Consider `merge` as an operation to merge a secondary SBOM into a master SBOM.
|
|
47
|
+
The metadata, license and siemens:profile of the master will be persisted in the result.
|
|
48
|
+
|
|
49
|
+
Remember KISS ("Keep it simple, stupid!"). Use this simple merge approach for the time being.
|
|
50
|
+
If there is the need to do better or a great idea on how to do better, we can change the approach.
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class MergeBom(capycli.common.script_base.ScriptBase):
|
|
55
|
+
"""Merge two SBOM files.
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
@staticmethod
|
|
59
|
+
def are_same(c1: Component, c2: Component, deep: bool = False) -> bool:
|
|
60
|
+
"""
|
|
61
|
+
Compares two components. If deep if False, then only
|
|
62
|
+
group, name and version are compared.
|
|
63
|
+
|
|
64
|
+
If deep is True, also properties and external references are compared.
|
|
65
|
+
"""
|
|
66
|
+
if (c1.group != c2.group) or (c1.name != c2.name) or (c1.version != c2.version):
|
|
67
|
+
return False
|
|
68
|
+
|
|
69
|
+
if deep:
|
|
70
|
+
# TBD
|
|
71
|
+
pass
|
|
72
|
+
|
|
73
|
+
return True
|
|
74
|
+
|
|
75
|
+
def find_in_bom(self, bom: Bom, component: Component) -> Component or None:
|
|
76
|
+
"""Searches for an item with the given name and version in the given SBOM."""
|
|
77
|
+
for c in bom.components:
|
|
78
|
+
if self.are_same(c, component):
|
|
79
|
+
return c
|
|
80
|
+
|
|
81
|
+
return None
|
|
82
|
+
|
|
83
|
+
def merge_boms(self, bom_old: Bom, bom_new: Bom) -> Bom:
|
|
84
|
+
for component_new in bom_new.components:
|
|
85
|
+
found = self.find_in_bom(bom_old, component_new)
|
|
86
|
+
if not found:
|
|
87
|
+
bom_old.components.add(component_new)
|
|
88
|
+
|
|
89
|
+
return bom_old
|
|
90
|
+
|
|
91
|
+
def run(self, args):
|
|
92
|
+
"""Main method()"""
|
|
93
|
+
if args.debug:
|
|
94
|
+
global LOG
|
|
95
|
+
LOG = capycli.get_logger(__name__)
|
|
96
|
+
|
|
97
|
+
print_text(
|
|
98
|
+
"\n" + capycli.APP_NAME + ", " + capycli.get_app_version() +
|
|
99
|
+
" - Merge two SBOM files.\n")
|
|
100
|
+
|
|
101
|
+
if args.help:
|
|
102
|
+
print("usage: CaPyCli bom merge [-h] [-v] bomfile1 bomfile2 [outputfile]")
|
|
103
|
+
print("")
|
|
104
|
+
print("positional arguments:")
|
|
105
|
+
print(" bomfile1 first bill of material, JSON")
|
|
106
|
+
print(" bomfile2 second bill of material, JSON")
|
|
107
|
+
print("")
|
|
108
|
+
print("optional arguments:")
|
|
109
|
+
print(" -h, --help show this help message and exit")
|
|
110
|
+
print(" outputfile if outputfile is specified the new SBOM will be written")
|
|
111
|
+
print(" to this file. Default is overwrite bomfile1")
|
|
112
|
+
return
|
|
113
|
+
|
|
114
|
+
if len(args.command) < 4:
|
|
115
|
+
print_red("Not enough input files specified!")
|
|
116
|
+
sys.exit(ResultCode.RESULT_COMMAND_ERROR)
|
|
117
|
+
|
|
118
|
+
if not os.path.isfile(args.command[2]):
|
|
119
|
+
print_red("First SBOM file not found!")
|
|
120
|
+
sys.exit(ResultCode.RESULT_FILE_NOT_FOUND)
|
|
121
|
+
|
|
122
|
+
if not os.path.isfile(args.command[3]):
|
|
123
|
+
print_red("Second SBOM file not found!")
|
|
124
|
+
sys.exit(ResultCode.RESULT_FILE_NOT_FOUND)
|
|
125
|
+
|
|
126
|
+
output = args.command[2]
|
|
127
|
+
if len(args.command) == 5:
|
|
128
|
+
output = args.command[4]
|
|
129
|
+
|
|
130
|
+
print_text("Loading first SBOM file", args.command[2])
|
|
131
|
+
try:
|
|
132
|
+
bom_old = CaPyCliBom.read_sbom(args.command[2])
|
|
133
|
+
except Exception as ex:
|
|
134
|
+
print_red("Error reading input SBOM file: " + repr(ex))
|
|
135
|
+
sys.exit(ResultCode.RESULT_ERROR_READING_BOM)
|
|
136
|
+
print_text(" ", self.get_comp_count_text(bom_old), "read from SBOM")
|
|
137
|
+
|
|
138
|
+
print_text("Loading second SBOM file", args.command[3])
|
|
139
|
+
try:
|
|
140
|
+
bom_new = CaPyCliBom.read_sbom(args.command[3])
|
|
141
|
+
except Exception as ex:
|
|
142
|
+
print_red("Error reading input SBOM file: " + repr(ex))
|
|
143
|
+
sys.exit(ResultCode.RESULT_ERROR_READING_BOM)
|
|
144
|
+
print_text(" ", self.get_comp_count_text(bom_new), "read from SBOM")
|
|
145
|
+
|
|
146
|
+
bom_merged = self.merge_boms(bom_old, bom_new)
|
|
147
|
+
|
|
148
|
+
print_text("Writing combined SBOM with", self.get_comp_count_text(bom_merged), "to", output)
|
|
149
|
+
try:
|
|
150
|
+
SbomWriter.write_to_json(bom_merged, output, True)
|
|
151
|
+
except Exception as ex:
|
|
152
|
+
print_red("Error writing updated SBOM file: " + repr(ex))
|
|
153
|
+
sys.exit(ResultCode.RESULT_ERROR_WRITING_BOM)
|
|
154
|
+
|
|
155
|
+
print_text()
|
capycli/bom/plaintext.py
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# -------------------------------------------------------------------------------
|
|
2
|
+
# Copyright (c) 2023 Siemens
|
|
3
|
+
# All Rights Reserved.
|
|
4
|
+
# Author: thomas.graf@siemens.com
|
|
5
|
+
#
|
|
6
|
+
# SPDX-License-Identifier: MIT
|
|
7
|
+
# -------------------------------------------------------------------------------
|
|
8
|
+
|
|
9
|
+
from cyclonedx.model.component import Component
|
|
10
|
+
|
|
11
|
+
from capycli import LOG
|
|
12
|
+
from capycli.main.exceptions import CaPyCliException
|
|
13
|
+
|
|
14
|
+
# -------------------------------------
|
|
15
|
+
# Expected File Format
|
|
16
|
+
#
|
|
17
|
+
# <component name>, <component version>
|
|
18
|
+
#
|
|
19
|
+
# Example
|
|
20
|
+
# python, 3.8
|
|
21
|
+
# colorama, 0.4.3
|
|
22
|
+
# wheel, 0.34.2
|
|
23
|
+
# tomli, 2.0.1
|
|
24
|
+
# -------------------------------------
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class PlainTextSupport():
|
|
28
|
+
@classmethod
|
|
29
|
+
def flatlist_to_cdx_components(cls, inputfile: str) -> list[Component]:
|
|
30
|
+
"""Convert a flat list of components to a list
|
|
31
|
+
of CycloneDX components."""
|
|
32
|
+
bom = []
|
|
33
|
+
LOG.debug(f"Reading from file {inputfile}")
|
|
34
|
+
try:
|
|
35
|
+
with open(inputfile, encoding="utf-8") as fin:
|
|
36
|
+
for line in fin:
|
|
37
|
+
line = line.strip()
|
|
38
|
+
parts = line.split(",")
|
|
39
|
+
|
|
40
|
+
if len(parts) < 2:
|
|
41
|
+
continue
|
|
42
|
+
|
|
43
|
+
name = parts[0].strip()
|
|
44
|
+
version = parts[1].strip()
|
|
45
|
+
LOG.debug(f" Reading from text: name={name}, version={version}")
|
|
46
|
+
cxcomp = Component(
|
|
47
|
+
name=name,
|
|
48
|
+
version=version)
|
|
49
|
+
|
|
50
|
+
bom.append(cxcomp)
|
|
51
|
+
except Exception as exp:
|
|
52
|
+
raise CaPyCliException("Error reading text file: " + str(exp))
|
|
53
|
+
|
|
54
|
+
LOG.debug("done")
|
|
55
|
+
return bom
|
|
56
|
+
|
|
57
|
+
@classmethod
|
|
58
|
+
def write_cdx_components_as_flatlist(cls, bom: list[Component], outputfile: str) -> None:
|
|
59
|
+
LOG.debug(f"Writing to file {outputfile}")
|
|
60
|
+
try:
|
|
61
|
+
with open(outputfile, "w", encoding="utf-8") as fout:
|
|
62
|
+
for cx_comp in bom:
|
|
63
|
+
name = cx_comp.name
|
|
64
|
+
version = cx_comp.version
|
|
65
|
+
fout.write(f"{name}, {version}\n")
|
|
66
|
+
except Exception as exp:
|
|
67
|
+
raise CaPyCliException("Error writing text file: " + str(exp))
|
|
68
|
+
|
|
69
|
+
LOG.debug("done")
|
capycli/bom/show_bom.py
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# -------------------------------------------------------------------------------
|
|
2
|
+
# Copyright (c) 2019-23 Siemens
|
|
3
|
+
# All Rights Reserved.
|
|
4
|
+
# Author: thomas.graf@siemens.com
|
|
5
|
+
#
|
|
6
|
+
# SPDX-License-Identifier: MIT
|
|
7
|
+
# -------------------------------------------------------------------------------
|
|
8
|
+
|
|
9
|
+
"""
|
|
10
|
+
Display the contents of a SBOM.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import os
|
|
14
|
+
import sys
|
|
15
|
+
|
|
16
|
+
from cyclonedx.model.bom import Bom
|
|
17
|
+
|
|
18
|
+
import capycli.common.script_base
|
|
19
|
+
from capycli.common.capycli_bom_support import CaPyCliBom, CycloneDxSupport
|
|
20
|
+
from capycli.common.print import print_red, print_text, print_yellow
|
|
21
|
+
from capycli.main.result_codes import ResultCode
|
|
22
|
+
|
|
23
|
+
LOG = capycli.get_logger(__name__)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class ShowBom(capycli.common.script_base.ScriptBase):
|
|
27
|
+
"""Print SBOM contents to stdout"""
|
|
28
|
+
def display_bom(self, bom: Bom, verbose: bool) -> None:
|
|
29
|
+
if not bom:
|
|
30
|
+
print_yellow(" Empty SBOM!")
|
|
31
|
+
return
|
|
32
|
+
|
|
33
|
+
for bomitem in bom.components:
|
|
34
|
+
print_text(" " + bomitem.name + ", " + bomitem.version)
|
|
35
|
+
|
|
36
|
+
if verbose:
|
|
37
|
+
if bomitem.purl:
|
|
38
|
+
print_text(" package-url:" + bomitem.purl)
|
|
39
|
+
|
|
40
|
+
sw360id = CycloneDxSupport.get_property_value(bomitem, CycloneDxSupport.CDX_PROP_SW360ID)
|
|
41
|
+
if sw360id:
|
|
42
|
+
print_text(" SW360 id:" + sw360id)
|
|
43
|
+
|
|
44
|
+
print_text("\n" + str(len(bom.components)) + " items in bill of material\n")
|
|
45
|
+
|
|
46
|
+
def run(self, args):
|
|
47
|
+
"""Main method()"""
|
|
48
|
+
if args.debug:
|
|
49
|
+
global LOG
|
|
50
|
+
LOG = capycli.get_logger(__name__)
|
|
51
|
+
|
|
52
|
+
print_text("\n" + capycli.APP_NAME + ", " + capycli.get_app_version() + " - Print SBOM contents to stdout\n")
|
|
53
|
+
|
|
54
|
+
if args.help:
|
|
55
|
+
print("usage: capycli bom show [-h] -i bomfile")
|
|
56
|
+
print("")
|
|
57
|
+
print("optional arguments:")
|
|
58
|
+
print("-h, --help show this help message and exit")
|
|
59
|
+
print("-i INPUTFILE input file to read from (JSON)")
|
|
60
|
+
print("-v be verbose")
|
|
61
|
+
return
|
|
62
|
+
|
|
63
|
+
if not args.inputfile:
|
|
64
|
+
LOG.error("No input file specified!")
|
|
65
|
+
sys.exit(ResultCode.RESULT_COMMAND_ERROR)
|
|
66
|
+
|
|
67
|
+
if not os.path.isfile(args.inputfile):
|
|
68
|
+
LOG.error("Input file not found!")
|
|
69
|
+
sys.exit(ResultCode.RESULT_FILE_NOT_FOUND)
|
|
70
|
+
|
|
71
|
+
try:
|
|
72
|
+
bom = CaPyCliBom.read_sbom(args.inputfile)
|
|
73
|
+
except Exception as ex:
|
|
74
|
+
print_red("Error reading SBOM: " + repr(ex))
|
|
75
|
+
sys.exit(ResultCode.RESULT_ERROR_READING_BOM)
|
|
76
|
+
|
|
77
|
+
self.display_bom(bom, args.verbose)
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# -------------------------------------------------------------------------------
|
|
2
|
+
# Copyright (c) 2019-2023 Siemens
|
|
3
|
+
# All Rights Reserved.
|
|
4
|
+
# Author: thomas.graf@siemens.com
|
|
5
|
+
#
|
|
6
|
+
# SPDX-License-Identifier: MIT
|
|
7
|
+
# -------------------------------------------------------------------------------
|
|
8
|
+
|
|
9
|
+
"""Module containing the common support methods."""
|