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.
Files changed (78) hide show
  1. License.md +27 -0
  2. capycli/__init__.py +214 -0
  3. capycli/__main__.py +13 -0
  4. capycli/bom/__init__.py +10 -0
  5. capycli/bom/bom_convert.py +163 -0
  6. capycli/bom/check_bom.py +187 -0
  7. capycli/bom/check_bom_item_status.py +197 -0
  8. capycli/bom/check_granularity.py +244 -0
  9. capycli/bom/create_components.py +644 -0
  10. capycli/bom/csv.py +69 -0
  11. capycli/bom/diff_bom.py +279 -0
  12. capycli/bom/download_sources.py +227 -0
  13. capycli/bom/filter_bom.py +323 -0
  14. capycli/bom/findsources.py +278 -0
  15. capycli/bom/handle_bom.py +134 -0
  16. capycli/bom/html.py +67 -0
  17. capycli/bom/legacy.py +312 -0
  18. capycli/bom/legacy_cx.py +151 -0
  19. capycli/bom/map_bom.py +1039 -0
  20. capycli/bom/merge_bom.py +155 -0
  21. capycli/bom/plaintext.py +69 -0
  22. capycli/bom/show_bom.py +77 -0
  23. capycli/common/__init__.py +9 -0
  24. capycli/common/capycli_bom_support.py +629 -0
  25. capycli/common/comparable_version.py +161 -0
  26. capycli/common/component_cache.py +240 -0
  27. capycli/common/dependencies_base.py +48 -0
  28. capycli/common/file_support.py +28 -0
  29. capycli/common/html_support.py +119 -0
  30. capycli/common/json_support.py +36 -0
  31. capycli/common/map_result.py +116 -0
  32. capycli/common/print.py +55 -0
  33. capycli/common/purl_service.py +169 -0
  34. capycli/common/purl_store.py +100 -0
  35. capycli/common/purl_utils.py +85 -0
  36. capycli/common/script_base.py +165 -0
  37. capycli/common/script_support.py +78 -0
  38. capycli/data/__init__.py +9 -0
  39. capycli/data/granularity_list.csv +1338 -0
  40. capycli/dependencies/__init__.py +9 -0
  41. capycli/dependencies/handle_dependencies.py +70 -0
  42. capycli/dependencies/javascript.py +261 -0
  43. capycli/dependencies/maven_list.py +333 -0
  44. capycli/dependencies/maven_pom.py +150 -0
  45. capycli/dependencies/nuget.py +184 -0
  46. capycli/dependencies/python.py +345 -0
  47. capycli/main/__init__.py +9 -0
  48. capycli/main/application.py +165 -0
  49. capycli/main/argument_parser.py +101 -0
  50. capycli/main/cli.py +28 -0
  51. capycli/main/exceptions.py +14 -0
  52. capycli/main/options.py +424 -0
  53. capycli/main/result_codes.py +41 -0
  54. capycli/mapping/handle_mapping.py +46 -0
  55. capycli/mapping/mapping_to_html.py +182 -0
  56. capycli/mapping/mapping_to_xlsx.py +197 -0
  57. capycli/moverview/handle_moverview.py +46 -0
  58. capycli/moverview/moverview_to_html.py +122 -0
  59. capycli/moverview/moverview_to_xlsx.py +170 -0
  60. capycli/project/__init__.py +9 -0
  61. capycli/project/check_prerequisites.py +304 -0
  62. capycli/project/create_bom.py +190 -0
  63. capycli/project/create_project.py +335 -0
  64. capycli/project/create_readme.py +546 -0
  65. capycli/project/find_project.py +128 -0
  66. capycli/project/get_license_info.py +246 -0
  67. capycli/project/handle_project.py +118 -0
  68. capycli/project/show_ecc.py +200 -0
  69. capycli/project/show_licenses.py +211 -0
  70. capycli/project/show_project.py +215 -0
  71. capycli/project/show_vulnerabilities.py +238 -0
  72. capycli-2.0.0.dev8.dist-info/LICENSES/CC0-1.0.txt +121 -0
  73. capycli-2.0.0.dev8.dist-info/LICENSES/MIT.txt +27 -0
  74. capycli-2.0.0.dev8.dist-info/License.md +27 -0
  75. capycli-2.0.0.dev8.dist-info/METADATA +268 -0
  76. capycli-2.0.0.dev8.dist-info/RECORD +78 -0
  77. capycli-2.0.0.dev8.dist-info/WHEEL +4 -0
  78. capycli-2.0.0.dev8.dist-info/entry_points.txt +3 -0
License.md ADDED
@@ -0,0 +1,27 @@
1
+ <!--
2
+ # SPDX-FileCopyrightText: (c) 2018-2023 Siemens
3
+ # SPDX-License-Identifier: MIT
4
+ -->
5
+
6
+ # MIT License
7
+
8
+ Copyright (c) 2019-2023 Siemens
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
11
+ this software and associated documentation files (the "Software"), to deal in
12
+ the Software without restriction, including without limitation the rights to
13
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
14
+ of the Software, and to permit persons to whom the Software is furnished to do
15
+ so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice (including the next
18
+ paragraph) shall be included in all copies or substantial portions of the
19
+ Software.
20
+
21
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27
+ SOFTWARE.
capycli/__init__.py ADDED
@@ -0,0 +1,214 @@
1
+ # -------------------------------------------------------------------------------
2
+ # Copyright (c) 2019-23 Siemens
3
+ # All Rights Reserved.
4
+ # Author: thomas.graf@siemens.com
5
+ #
6
+ # SPDX-License-Identifier: MIT
7
+ # SPDX-FileCopyrightText: (c) 2019-2023 Siemens
8
+ # -------------------------------------------------------------------------------
9
+
10
+ """Top-level module for CaPyCli.
11
+
12
+ This module
13
+ - initializes logging for the command-line tool
14
+ - tracks the version of the package
15
+ - provides a way to configure logging for the command-line tool
16
+ """
17
+
18
+ import importlib
19
+ import logging
20
+ import sys
21
+ from typing import Any
22
+
23
+ import tomli
24
+ from colorama import Fore, Style, init
25
+
26
+ APP_NAME = "CaPyCli"
27
+ VERBOSITY_LEVEL = 1
28
+
29
+
30
+ def is_debug_logging_enabled() -> bool:
31
+ return VERBOSITY_LEVEL > 1
32
+
33
+
34
+ def _get_project_meta() -> Any:
35
+ """Read version information from poetry configuration file."""
36
+ try:
37
+ with open('pyproject.toml', mode='rb') as pyproject:
38
+ return tomli.load(pyproject)['tool']['poetry']
39
+ except Exception:
40
+ # ignore all errors
41
+ pass
42
+
43
+
44
+ def get_app_version() -> str:
45
+ """Get the version string of this application"""
46
+ version = ""
47
+ try:
48
+ # this will only work when the package has been installed
49
+ version = importlib.metadata.version("capycli")
50
+ except: # noqa
51
+ pass
52
+
53
+ if not version:
54
+ # use version information from poetry
55
+ pkg_meta = _get_project_meta()
56
+ version = str(pkg_meta['version'])
57
+
58
+ if not version:
59
+ version = "0.0.0-no-version"
60
+
61
+ return version
62
+
63
+
64
+ # There is nothing lower than logging.DEBUG (10) in the logging library,
65
+ # but we want an extra level to avoid being too verbose when using -vv.
66
+ _EXTRA_VERBOSE = 5
67
+ logging.addLevelName(_EXTRA_VERBOSE, "VERBOSE")
68
+
69
+ _VERBOSITY_TO_LOG_LEVEL = {
70
+ # output more than warnings but not debugging info
71
+ 1: logging.INFO, # INFO is a numerical level of 20
72
+ # output debugging information
73
+ 2: logging.DEBUG, # DEBUG is a numerical level of 10
74
+ # output extra verbose debugging information
75
+ 3: _EXTRA_VERBOSE,
76
+ }
77
+
78
+ # initialize colorama
79
+ init()
80
+
81
+
82
+ class ConsoleHandler(logging.Handler):
83
+ """Handler that write to stderr for errors and to stdout
84
+ for other logiing records."""
85
+ def __init__(self) -> None:
86
+ super().__init__()
87
+
88
+ def emit(self, record: logging.LogRecord) -> None:
89
+ """Emit a record."""
90
+ try:
91
+ msg = self.format(record)
92
+ if record.levelno >= 40:
93
+ # error, critical
94
+ sys.stderr.write(msg)
95
+ elif record.levelno >= 30:
96
+ # warning
97
+ sys.stderr.write(msg)
98
+ print(msg)
99
+ else:
100
+ # info, debug, all other
101
+ print(msg)
102
+ except Exception:
103
+ self.handleError(record)
104
+
105
+
106
+ class ColorFormatter(logging.Formatter):
107
+ """
108
+ A logging formatter for color cosole output.
109
+ Critical messages and errors are displayed in red.
110
+ Warnings are displayed in yellow.
111
+ Infos are displayed in white.
112
+ Debug messages are displayed in blue.
113
+ """
114
+ def __init__(self, verbosity: int) -> None:
115
+ super().__init__()
116
+ self.verbosity = verbosity
117
+ self.fmt = "%(asctime)s:%(levelname)s:%(name)s: %(message)s"
118
+ if self.verbosity == 1:
119
+ self.fmt = "%(message)s"
120
+
121
+ def get_color_format(self, levelno: int, fmt: str) -> Any:
122
+ if levelno >= 50:
123
+ color = Fore.LIGHTRED_EX
124
+ elif levelno >= 40:
125
+ color = Fore.LIGHTRED_EX
126
+ elif levelno >= 30:
127
+ color = Fore.LIGHTYELLOW_EX
128
+ elif levelno >= 20:
129
+ color = Fore.LIGHTWHITE_EX
130
+ elif levelno >= 10:
131
+ color = Fore.LIGHTBLUE_EX
132
+ else:
133
+ color = Fore.WHITE
134
+
135
+ return color + fmt + Style.RESET_ALL
136
+
137
+ def format(self, record: logging.LogRecord) -> str:
138
+ log_fmt = self.get_color_format(record.levelno, self.fmt)
139
+ formatter = logging.Formatter(log_fmt)
140
+ return formatter.format(record)
141
+
142
+
143
+ class ColoredLogger(logging.Logger):
144
+ """
145
+ A color console logger that uses ColorFormatter
146
+ to display colored log messages and uses ConsoleHandler
147
+ to output critical messages, errors and warnings to stderr.
148
+ Infos and debug messages will be sent to stdout.
149
+ """
150
+ def __init__(self, name: str):
151
+ logging.Logger.__init__(self, name, logging.DEBUG)
152
+
153
+ self.propagate = False
154
+ self.setVerbosity(1)
155
+
156
+ def getVerbosity(self) -> int:
157
+ return self.__verbosity
158
+
159
+ def setVerbosity(self, value: int) -> None:
160
+ self.__verbosity = value
161
+ console = ConsoleHandler()
162
+ color_formatter = ColorFormatter(self.__verbosity)
163
+ console.setFormatter(color_formatter)
164
+ self.handlers.clear()
165
+ self.addHandler(console)
166
+
167
+ def handle(self, record: logging.LogRecord) -> None:
168
+ if self.isEnabledFor(record.levelno):
169
+ return super().handle(record)
170
+
171
+
172
+ def configure_logging(verbosity: int) -> logging.Logger:
173
+ """
174
+ Configure logging.
175
+
176
+ :param int verbosity:
177
+ How verbose to be in logging information.
178
+ """
179
+ logging.setLoggerClass(ColoredLogger)
180
+
181
+ global VERBOSITY_LEVEL
182
+ VERBOSITY_LEVEL = verbosity
183
+
184
+ if verbosity < 0:
185
+ verbosity = 0
186
+ if verbosity > 3:
187
+ verbosity = 3
188
+
189
+ log_level = _VERBOSITY_TO_LOG_LEVEL[verbosity]
190
+
191
+ # log_level = logging.DEBUG # too much output from other libraries
192
+ logging.basicConfig(level=log_level)
193
+
194
+ logger = logging.getLogger(__name__)
195
+ logger.setVerbosity(verbosity) # type: ignore
196
+ logger.setLevel(log_level)
197
+
198
+ global LOG
199
+ LOG = logger
200
+
201
+ return logger
202
+
203
+
204
+ def get_logger(name: str) -> logging.Logger:
205
+ """Get one of our colored loggers for the specified name."""
206
+ logger = logging.getLogger(name)
207
+ logger.setVerbosity(VERBOSITY_LEVEL) # type: ignore
208
+ log_level = _VERBOSITY_TO_LOG_LEVEL[VERBOSITY_LEVEL]
209
+ logger.setLevel(log_level)
210
+ return logger
211
+
212
+
213
+ # Initialize logging
214
+ LOG = configure_logging(1)
capycli/__main__.py ADDED
@@ -0,0 +1,13 @@
1
+ # -------------------------------------------------------------------------------
2
+ # Copyright (c) 2019-23 Siemens
3
+ # All Rights Reserved.
4
+ # Author: thomas.graf@siemens.com
5
+ #
6
+ # SPDX-License-Identifier: MIT
7
+ # SPDX-FileCopyrightText: (c) 2019-2023 Siemens
8
+ # -------------------------------------------------------------------------------
9
+
10
+ """Module allowing for ``python -m CaPyCli ...``."""
11
+ from capycli.main import cli
12
+
13
+ cli.main()
@@ -0,0 +1,10 @@
1
+ # -------------------------------------------------------------------------------
2
+ # Copyright (c) 2019-23 Siemens
3
+ # All Rights Reserved.
4
+ # Author: thomas.graf@siemens.com
5
+ #
6
+ # SPDX-License-Identifier: MIT
7
+ # SPDX-FileCopyrightText: (c) 2018-2023 Siemens
8
+ # -------------------------------------------------------------------------------
9
+
10
+ """SBOM specific methods"""
@@ -0,0 +1,163 @@
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 enum import StrEnum # not supported in Python 3.10.3
13
+ from enum import Enum
14
+ from typing import Any
15
+
16
+ import capycli.common.json_support
17
+ import capycli.common.script_base
18
+ from capycli import get_logger
19
+ from capycli.common.capycli_bom_support import CaPyCliBom
20
+ from capycli.common.print import print_red, print_text
21
+ from capycli.main.exceptions import CaPyCliException
22
+ from capycli.main.result_codes import ResultCode
23
+
24
+ from .csv import CsvSupport
25
+ from .html import HtmlConversionSupport
26
+ from .legacy import LegacySupport
27
+ from .legacy_cx import LegacyCx
28
+ from .plaintext import PlainTextSupport
29
+
30
+ LOG = get_logger(__name__)
31
+
32
+
33
+ class BomFormat(str, Enum):
34
+ # CaPyCLI flavor of Siemens Standard BOM/CycloneDX
35
+ CAPYCLI = "capycli"
36
+ # Siemens Standard BOM
37
+ SBOM = "sbom"
38
+ # plain text
39
+ TEXT = "text"
40
+ # CSV
41
+ CSV = "csv"
42
+ # CaPyCLI JSON
43
+ LEGACY = "legacy"
44
+ # CaPyCLI CycloneDX
45
+ LEGACY_CX = "legacy-cx"
46
+ # HTML
47
+ HTML = "html"
48
+
49
+
50
+ class BomConvert(capycli.common.script_base.ScriptBase):
51
+ def convert(self,
52
+ inputfile: str,
53
+ inputformat: str,
54
+ outputfile: str,
55
+ outputformat: str) -> None:
56
+ """Main conversion method."""
57
+ if not outputformat:
58
+ # default is CaPyCLI
59
+ outputformat = BomFormat.CAPYCLI
60
+
61
+ cdx_components = []
62
+ project = None
63
+ sbom = None
64
+ try:
65
+ if inputformat == BomFormat.TEXT:
66
+ cdx_components = PlainTextSupport.flatlist_to_cdx_components(inputfile)
67
+ print_text(f" {len(cdx_components)} components read from file {inputfile}")
68
+ elif inputformat == BomFormat.CSV:
69
+ cdx_components = CsvSupport.csv_to_cdx_components(inputfile)
70
+ print_text(f" {len(cdx_components)} components read from file {inputfile}")
71
+ elif (inputformat == BomFormat.CAPYCLI) or (inputformat == BomFormat.SBOM):
72
+ sbom = CaPyCliBom.read_sbom(inputfile)
73
+ cdx_components = sbom.components
74
+ project = sbom.metadata.component
75
+ print_text(f" {len(cdx_components)} components read from file {inputfile}")
76
+ elif inputformat == BomFormat.LEGACY:
77
+ cdx_components = LegacySupport.legacy_to_cdx_components(inputfile)
78
+ print_text(f" {len(cdx_components)} components read from file {inputfile}")
79
+ elif inputformat == BomFormat.LEGACY_CX:
80
+ sbom = LegacyCx.read_sbom(inputfile)
81
+ cdx_components = sbom.components
82
+ print_text(f" {len(cdx_components)} components read from file {inputfile}")
83
+ else:
84
+ print_red("Unsupported input format!")
85
+ sys.exit(ResultCode.RESULT_COMMAND_ERROR)
86
+ except CaPyCliException as error:
87
+ LOG.error(f"Error processing input file: {str(error)}")
88
+ sys.exit(ResultCode.RESULT_COMMAND_ERROR)
89
+
90
+ try:
91
+ if outputformat == BomFormat.TEXT:
92
+ PlainTextSupport.write_cdx_components_as_flatlist(cdx_components, outputfile)
93
+ print_text(f" {len(cdx_components)} components written to file {outputfile}")
94
+ elif outputformat == BomFormat.CSV:
95
+ CsvSupport.write_cdx_components_as_csv(cdx_components, outputfile)
96
+ print_text(f" {len(cdx_components)} components written to file {outputfile}")
97
+ elif outputformat == BomFormat.HTML:
98
+ HtmlConversionSupport.write_cdx_components_as_html(cdx_components, outputfile, project)
99
+ print_text(f" {len(cdx_components)} components written to file {outputfile}")
100
+ elif outputformat == BomFormat.CAPYCLI:
101
+ if sbom:
102
+ CaPyCliBom.write_sbom(sbom, outputfile)
103
+ print_text(f" {len(sbom.components)} components written to file {outputfile}")
104
+ else:
105
+ CaPyCliBom.write_simple_sbom(cdx_components, outputfile)
106
+ print_text(f" {len(cdx_components)} components written to file {outputfile}")
107
+ elif outputformat == BomFormat.LEGACY:
108
+ LegacySupport.write_cdx_components_as_legacy(cdx_components, outputfile)
109
+ print_text(f" {len(cdx_components)} components written to file {outputfile}")
110
+ else:
111
+ LOG.error("Unsupported output format!")
112
+ sys.exit(ResultCode.RESULT_COMMAND_ERROR)
113
+ except CaPyCliException as error:
114
+ LOG.error(f"Error creating output file: {str(error)}")
115
+ sys.exit(ResultCode.RESULT_COMMAND_ERROR)
116
+
117
+ def check_arguments(self, args: Any) -> None:
118
+ """Check input arguments."""
119
+ if not args.inputfile:
120
+ LOG.error("No input file specified!")
121
+ sys.exit(ResultCode.RESULT_COMMAND_ERROR)
122
+
123
+ if not os.path.isfile(args.inputfile):
124
+ LOG.error("Input file not found!")
125
+ sys.exit(ResultCode.RESULT_FILE_NOT_FOUND)
126
+
127
+ if not args.inputformat:
128
+ LOG.error("No input format specified!")
129
+ sys.exit(ResultCode.RESULT_COMMAND_ERROR)
130
+
131
+ if not args.outputfile:
132
+ LOG.error("No output file specified!")
133
+ sys.exit(ResultCode.RESULT_COMMAND_ERROR)
134
+
135
+ if not args.outputformat:
136
+ LOG.warning("No output format specified, defaulting to sbom")
137
+
138
+ def display_help(self) -> None:
139
+ """Display (local) help."""
140
+ print("usage: CaPyCli bom convert [-h] [-i INPUTFILE] [-if {capycli,text,csv,legacy,legacy-cx}]")
141
+ print(" [-o OUTPUTFILE] [-of {capycli,text,csv,legacy,legacy-cx,html}]")
142
+ print("")
143
+ print("optional arguments:")
144
+ print(" -h, --help Show this help message and exit")
145
+ print(" -i INPUTFILE Input BOM filename (JSON)")
146
+ print(" -o OUTPUTFILE Output BOM filename")
147
+ print(" -if INPUTFORMAT Specify input file format: capycli|sbom|text|csv|legacy|legacy-cx")
148
+ print(" -of OUTPUTFORMAT Specify output file format: capycli|text|csv|legacy|html")
149
+
150
+ def run(self, args):
151
+ """Main method()"""
152
+ print("\n" + capycli.APP_NAME + ", " + capycli.get_app_version() + " - Convert SBOM formats\n")
153
+
154
+ if args.help:
155
+ self.display_help()
156
+ return
157
+
158
+ self.check_arguments(args)
159
+ if args.debug:
160
+ global LOG
161
+ LOG = get_logger(__name__)
162
+
163
+ self.convert(args.inputfile, args.inputformat, args.outputfile, args.outputformat)
@@ -0,0 +1,187 @@
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
+ import logging
10
+ import os
11
+ import sys
12
+
13
+ import requests
14
+ import sw360.sw360_api
15
+ from colorama import Fore, Style
16
+ from cyclonedx.model.bom import Bom
17
+ from cyclonedx.model.component import Component
18
+
19
+ import capycli.common.script_base
20
+ from capycli.common.capycli_bom_support import CaPyCliBom, CycloneDxSupport
21
+ from capycli.common.print import print_green, print_red, print_text, print_yellow
22
+ from capycli.main.result_codes import ResultCode
23
+
24
+ LOG = capycli.get_logger(__name__)
25
+
26
+
27
+ class CheckBom(capycli.common.script_base.ScriptBase):
28
+ """
29
+ Check that all releases listed in the SBOM really exist
30
+ """
31
+
32
+ def _bom_has_items_without_id(self, bom: Bom) -> bool:
33
+ """Determines whether there is at least one SBOM item
34
+ without Sw360Id."""
35
+ for item in bom.components:
36
+ sw360id = CycloneDxSupport.get_property_value(item, CycloneDxSupport.CDX_PROP_SW360ID)
37
+ if not sw360id:
38
+ return True
39
+
40
+ return False
41
+
42
+ def _find_by_id(self, component: Component) -> dict:
43
+ sw360id = CycloneDxSupport.get_property_value(component, CycloneDxSupport.CDX_PROP_SW360ID)
44
+ for step in range(3):
45
+ try:
46
+ release_details = self.client.get_release(sw360id)
47
+ return release_details
48
+ except sw360.sw360_api.SW360Error as swex:
49
+ if swex.response.status_code == requests.codes['not_found']:
50
+ print_yellow(
51
+ " Not found " + component.name +
52
+ ", " + component.version + ", " + sw360id)
53
+ break
54
+
55
+ # only report other errors if this is the third attempt
56
+ if step >= 2:
57
+ print(Fore.LIGHTRED_EX + " Error retrieving release data: ")
58
+ print(
59
+ " " + component.name + ", " + component.version +
60
+ ", " + sw360id)
61
+ print(" Status Code: " + str(swex.response.status_code))
62
+ if swex.message:
63
+ print(" Message: " + swex.message)
64
+ print(Style.RESET_ALL)
65
+
66
+ return None
67
+
68
+ def _find_by_name(self, component: Component) -> dict:
69
+ for step in range(3):
70
+ try:
71
+ releases = self.client.get_releases_by_name(component.name)
72
+ if not releases:
73
+ return None
74
+
75
+ for r in releases:
76
+ if r.get("version", "") == component.version:
77
+ return r
78
+
79
+ return None
80
+ except sw360.sw360_api.SW360Error as swex:
81
+ if swex.response.status_code == requests.codes['not_found']:
82
+ print_yellow(
83
+ " Not found " + component.name +
84
+ ", " + component.version)
85
+ break
86
+
87
+ # only report other errors if this is the third attempt
88
+ if step >= 2:
89
+ print(Fore.LIGHTRED_EX + " Error retrieving release data: ")
90
+ print(
91
+ " " + component.name + ", " + component.version)
92
+ print(" Status Code: " + str(swex.response.status_code))
93
+ if swex.message:
94
+ print(" Message: " + swex.message)
95
+ print(Style.RESET_ALL)
96
+
97
+ return None
98
+
99
+ def check_releases(self, bom: Bom) -> int:
100
+ """Checks for each release in the list whether it can be found on the specified
101
+ SW360 instance."""
102
+ found_count = 0
103
+ for component in bom.components:
104
+ release_details = None
105
+ sw360id = CycloneDxSupport.get_property_value(component, CycloneDxSupport.CDX_PROP_SW360ID)
106
+ if sw360id:
107
+ release_details = self._find_by_id(component)
108
+ else:
109
+ release_details = self._find_by_name(component)
110
+
111
+ if release_details:
112
+ sid = self.client.get_id_from_href(release_details["_links"]["self"]["href"])
113
+ print_green(
114
+ " Found " + release_details["name"] +
115
+ ", " + release_details["version"] + ", " + sid)
116
+ found_count += 1
117
+ continue
118
+
119
+ if not id:
120
+ print_yellow(
121
+ " " + component.name +
122
+ ", " + component.version +
123
+ " - No id available - skipping!")
124
+ continue
125
+
126
+ return found_count
127
+
128
+ def run(self, args):
129
+ """Main method()"""
130
+ if args.debug:
131
+ global LOG
132
+ LOG = capycli.get_logger(__name__)
133
+ else:
134
+ # suppress (debug) log output from requests and urllib
135
+ logging.getLogger("requests").setLevel(logging.WARNING)
136
+ logging.getLogger("urllib3").setLevel(logging.WARNING)
137
+ logging.getLogger("urllib3.connectionpool").setLevel(logging.WARNING)
138
+
139
+ print_text(
140
+ "\n" + capycli.APP_NAME + ", " + capycli.get_app_version() +
141
+ " - Check that all releases in the SBOM exist on target SW360 instance.\n")
142
+
143
+ if args.help:
144
+ print("usage: CaPyCli bom check [-h] [-t SW360_TOKEN] [-oa] [-url SW360_URL] [-v] -i bomfile")
145
+ print("")
146
+ print("optional arguments:")
147
+ print(" -h, --help show this help message and exit")
148
+ print(" -t SW360_TOKEN, SW360_TOKEN")
149
+ print(" use this token for access to SW360")
150
+ print(" -oa, --oauth2 this is an oauth2 token")
151
+ print(" -url SW360_URL use this URL for access to SW360")
152
+ print(" -i INPUTFILE SBOM file to read from")
153
+ print(" -v be verbose")
154
+ return
155
+
156
+ if not args.inputfile:
157
+ print_red("No input file specified!")
158
+ sys.exit(ResultCode.RESULT_COMMAND_ERROR)
159
+
160
+ if not os.path.isfile(args.inputfile):
161
+ print_red("Input file not found!")
162
+ sys.exit(ResultCode.RESULT_FILE_NOT_FOUND)
163
+
164
+ print("Loading SBOM file", args.inputfile)
165
+ try:
166
+ bom = CaPyCliBom.read_sbom(args.inputfile)
167
+ except Exception as ex:
168
+ print_red("Error loading SBOM: " + repr(ex))
169
+ sys.exit(ResultCode.RESULT_ERROR_READING_BOM)
170
+
171
+ if args.verbose:
172
+ print_text(" ", self.get_comp_count_text(bom), " read from SBOM")
173
+
174
+ if self._bom_has_items_without_id(bom):
175
+ print("There are SBOM items without Sw360 id - searching per name may take a little bit longer...")
176
+
177
+ if args.sw360_token and args.oauth2:
178
+ self.analyze_token(args.sw360_token)
179
+
180
+ if not self.login(token=args.sw360_token, url=args.sw360_url, oauth2=args.oauth2):
181
+ print_red("ERROR: login failed!")
182
+ sys.exit(ResultCode.RESULT_AUTH_ERROR)
183
+
184
+ found = self.check_releases(bom)
185
+
186
+ print()
187
+ print(len(bom.components), "components checked,", found, "successfully found.")