capycli 2.10.0.dev1__tar.gz → 2.10.1__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.10.0.dev1 → capycli-2.10.1}/PKG-INFO +5 -5
- {capycli-2.10.0.dev1 → capycli-2.10.1}/Readme.md +4 -4
- capycli-2.10.1/capycli/bom/bom_package.py +215 -0
- {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/bom/download_sources.py +7 -35
- {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/bom/handle_bom.py +9 -1
- {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/bom/show_bom.py +6 -3
- {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/dependencies/handle_dependencies.py +9 -1
- {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/dependencies/javascript.py +8 -4
- {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/dependencies/python.py +10 -9
- capycli-2.10.1/capycli/dependencies/rust.py +446 -0
- {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/main/options.py +2 -9
- {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/project/check_prerequisites.py +6 -2
- {capycli-2.10.0.dev1 → capycli-2.10.1}/pyproject.toml +1 -1
- {capycli-2.10.0.dev1 → capycli-2.10.1}/License.md +0 -0
- {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/__init__.py +0 -0
- {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/__main__.py +0 -0
- {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/bom/__init__.py +0 -0
- {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/bom/bom_convert.py +0 -0
- {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/bom/bom_validate.py +0 -0
- {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/bom/check_bom.py +0 -0
- {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/bom/check_bom_item_status.py +0 -0
- {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/bom/check_granularity.py +0 -0
- {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/bom/create_components.py +0 -0
- {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/bom/csv.py +0 -0
- {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/bom/diff_bom.py +0 -0
- {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/bom/filter_bom.py +0 -0
- {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/bom/findsources.py +0 -0
- {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/bom/html.py +0 -0
- {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/bom/legacy.py +0 -0
- {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/bom/legacy_cx.py +0 -0
- {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/bom/map_bom.py +0 -0
- {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/bom/merge_bom.py +0 -0
- {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/bom/plaintext.py +0 -0
- {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/common/__init__.py +0 -0
- {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/common/capycli_bom_support.py +0 -0
- {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/common/comparable_version.py +0 -0
- {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/common/component_cache.py +0 -0
- {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/common/dependencies_base.py +0 -0
- {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/common/file_support.py +0 -0
- {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/common/github_support.py +0 -0
- {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/common/html_support.py +0 -0
- {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/common/json_support.py +0 -0
- {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/common/map_result.py +0 -0
- {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/common/print.py +0 -0
- {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/common/purl_service.py +0 -0
- {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/common/purl_store.py +0 -0
- {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/common/purl_utils.py +0 -0
- {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/common/script_base.py +0 -0
- {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/common/script_support.py +0 -0
- {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/data/__init__.py +0 -0
- {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/data/granularity_list.csv +0 -0
- {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/dependencies/__init__.py +0 -0
- {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/dependencies/maven_list.py +0 -0
- {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/dependencies/maven_pom.py +0 -0
- {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/dependencies/nuget.py +0 -0
- {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/main/__init__.py +0 -0
- {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/main/application.py +0 -0
- {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/main/argument_parser.py +0 -0
- {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/main/cli.py +0 -0
- {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/main/exceptions.py +0 -0
- {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/main/result_codes.py +0 -0
- {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/mapping/handle_mapping.py +0 -0
- {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/mapping/mapping_to_html.py +0 -0
- {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/mapping/mapping_to_xlsx.py +0 -0
- {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/moverview/handle_moverview.py +0 -0
- {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/moverview/moverview_to_html.py +0 -0
- {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/moverview/moverview_to_xlsx.py +0 -0
- {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/project/__init__.py +0 -0
- {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/project/create_bom.py +0 -0
- {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/project/create_project.py +0 -0
- {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/project/create_readme.py +0 -0
- {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/project/find_project.py +0 -0
- {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/project/get_license_info.py +0 -0
- {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/project/handle_project.py +0 -0
- {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/project/show_ecc.py +0 -0
- {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/project/show_licenses.py +0 -0
- {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/project/show_project.py +0 -0
- {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/project/show_vulnerabilities.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: capycli
|
|
3
|
-
Version: 2.10.
|
|
3
|
+
Version: 2.10.1
|
|
4
4
|
Summary: CaPyCli - Clearing Automation Python Command Line Interface for SW360
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
License-File: License.md
|
|
@@ -38,17 +38,17 @@ Project-URL: issues, https://github.com/sw360/capycli/issues
|
|
|
38
38
|
Description-Content-Type: text/markdown
|
|
39
39
|
|
|
40
40
|
<!--
|
|
41
|
-
# SPDX-FileCopyrightText: (c) 2018-
|
|
41
|
+
# SPDX-FileCopyrightText: (c) 2018-2026 Siemens
|
|
42
42
|
# SPDX-License-Identifier: MIT
|
|
43
43
|
-->
|
|
44
44
|
|
|
45
|
-

|
|
45
|
+

|
|
46
46
|
|
|
47
47
|
# CaPyCli - Clearing Automation Python Command Line Tool for SW360
|
|
48
48
|
|
|
49
49
|
[](https://github.com/sw360/capycli/blob/main/License.md)
|
|
50
50
|
[](https://pypi.org/project/capycli/)
|
|
51
|
-
[](https://www.python.org/doc/versions/)
|
|
52
52
|
[](https://github.com/sw360/capycli/actions/workflows/static-checks.yml)
|
|
53
53
|
[](https://github.com/sw360/capycli/actions/workflows/unit-tests.yml)
|
|
54
54
|
[](https://github.com/sw360/capycli/actions/workflows/unit-tests.yml)
|
|
@@ -300,7 +300,7 @@ This is a Python project managed using ```Poetry```.
|
|
|
300
300
|
|
|
301
301
|
## Copyright & License
|
|
302
302
|
|
|
303
|
-
Copyright 2018-
|
|
303
|
+
Copyright 2018-2026 Siemens
|
|
304
304
|
|
|
305
305
|
This program and the accompanying materials are made
|
|
306
306
|
available under the terms of the MIT License.
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
<!--
|
|
2
|
-
# SPDX-FileCopyrightText: (c) 2018-
|
|
2
|
+
# SPDX-FileCopyrightText: (c) 2018-2026 Siemens
|
|
3
3
|
# SPDX-License-Identifier: MIT
|
|
4
4
|
-->
|
|
5
5
|
|
|
6
|
-

|
|
6
|
+

|
|
7
7
|
|
|
8
8
|
# CaPyCli - Clearing Automation Python Command Line Tool for SW360
|
|
9
9
|
|
|
10
10
|
[](https://github.com/sw360/capycli/blob/main/License.md)
|
|
11
11
|
[](https://pypi.org/project/capycli/)
|
|
12
|
-
[](https://www.python.org/doc/versions/)
|
|
13
13
|
[](https://github.com/sw360/capycli/actions/workflows/static-checks.yml)
|
|
14
14
|
[](https://github.com/sw360/capycli/actions/workflows/unit-tests.yml)
|
|
15
15
|
[](https://github.com/sw360/capycli/actions/workflows/unit-tests.yml)
|
|
@@ -261,7 +261,7 @@ This is a Python project managed using ```Poetry```.
|
|
|
261
261
|
|
|
262
262
|
## Copyright & License
|
|
263
263
|
|
|
264
|
-
Copyright 2018-
|
|
264
|
+
Copyright 2018-2026 Siemens
|
|
265
265
|
|
|
266
266
|
This program and the accompanying materials are made
|
|
267
267
|
available under the terms of the MIT License.
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
# -------------------------------------------------------------------------------
|
|
2
|
+
# Copyright (c) 2025 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 pathlib
|
|
12
|
+
import posixpath
|
|
13
|
+
import shutil
|
|
14
|
+
import sys
|
|
15
|
+
import tempfile
|
|
16
|
+
from typing import Any
|
|
17
|
+
|
|
18
|
+
from cyclonedx.model import ExternalReference, ExternalReferenceType, HashAlgorithm, HashType, XsUri
|
|
19
|
+
from cyclonedx.model.bom import Bom
|
|
20
|
+
|
|
21
|
+
import capycli.common.script_base
|
|
22
|
+
from capycli.bom.download_sources import BomDownloadSources
|
|
23
|
+
from capycli.common.capycli_bom_support import CaPyCliBom, CycloneDxSupport, SbomWriter
|
|
24
|
+
from capycli.common.print import print_red, print_text, print_yellow
|
|
25
|
+
from capycli.common.script_support import ScriptSupport
|
|
26
|
+
from capycli.main.result_codes import ResultCode
|
|
27
|
+
|
|
28
|
+
LOG = capycli.get_logger(__name__)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class BomPackage(capycli.common.script_base.ScriptBase):
|
|
32
|
+
"""
|
|
33
|
+
Create a single zip archive that contains the SBOM and all source and binary files.
|
|
34
|
+
The archive shoukd have the following structure:
|
|
35
|
+
|
|
36
|
+
sbom.cdx.json
|
|
37
|
+
+--- binaries
|
|
38
|
+
| +--- 77100a62c2e6f04b53977b9f541044d7d722693d
|
|
39
|
+
| | `--- some-binary.jar
|
|
40
|
+
| +--- 8031352b2bb0a49e67818bf04c027aa92e645d5c
|
|
41
|
+
| | `--- another-binary.jar
|
|
42
|
+
| `--- (... more ...)
|
|
43
|
+
`--- sources
|
|
44
|
+
+--- 6bb10559db88828dac3627de26974035a5dd4ddb
|
|
45
|
+
| `--- some-sources.jar
|
|
46
|
+
+--- 4d44e4edc4a7fb39f09b95b09f560a15976fa1ba
|
|
47
|
+
| `--- another-sources.jar
|
|
48
|
+
`--- (... more ...)
|
|
49
|
+
"""
|
|
50
|
+
def download_files(self, sbom: Bom, target_folder: str) -> None:
|
|
51
|
+
"""Download source and binary files for all items of the SBOM.
|
|
52
|
+
|
|
53
|
+
@params:
|
|
54
|
+
bom - Required : the bill of materials (BOM) (list)
|
|
55
|
+
target_folder - Required : folder to store the source files (string)
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
for component in sbom.components:
|
|
59
|
+
item_name = ScriptSupport.get_full_name_from_component(component)
|
|
60
|
+
print_text(" " + item_name)
|
|
61
|
+
|
|
62
|
+
# download source file
|
|
63
|
+
source_url = CycloneDxSupport.get_ext_ref_source_url(component)
|
|
64
|
+
if source_url:
|
|
65
|
+
result = BomDownloadSources.download_source_file(source_url._uri, target_folder)
|
|
66
|
+
else:
|
|
67
|
+
result = None
|
|
68
|
+
print_red(" No source URL specified!")
|
|
69
|
+
|
|
70
|
+
if result is not None:
|
|
71
|
+
(path, sha1) = result
|
|
72
|
+
# move file to appropriate location
|
|
73
|
+
filename = pathlib.Path(path).name
|
|
74
|
+
targetsha = os.path.join(target_folder, "sources", sha1)
|
|
75
|
+
os.makedirs(targetsha, exist_ok=True)
|
|
76
|
+
target = os.path.join(targetsha, filename)
|
|
77
|
+
shutil.move(path, target)
|
|
78
|
+
|
|
79
|
+
# update SBOM
|
|
80
|
+
new = False
|
|
81
|
+
ext_ref = CycloneDxSupport.get_ext_ref(
|
|
82
|
+
component, ExternalReferenceType.DISTRIBUTION, CaPyCliBom.SOURCE_FILE_COMMENT)
|
|
83
|
+
file_uri = posixpath.join("sources", sha1, filename)
|
|
84
|
+
if not file_uri.startswith("file://"):
|
|
85
|
+
file_uri = "file:///" + file_uri
|
|
86
|
+
if not ext_ref:
|
|
87
|
+
ext_ref = ExternalReference(
|
|
88
|
+
type=ExternalReferenceType.DISTRIBUTION,
|
|
89
|
+
comment=CaPyCliBom.SOURCE_FILE_COMMENT,
|
|
90
|
+
url=XsUri(file_uri))
|
|
91
|
+
new = True
|
|
92
|
+
else:
|
|
93
|
+
ext_ref.url = XsUri(file_uri)
|
|
94
|
+
ext_ref.hashes.add(HashType(
|
|
95
|
+
alg=HashAlgorithm.SHA_1,
|
|
96
|
+
content=sha1))
|
|
97
|
+
if new:
|
|
98
|
+
component.external_references.add(ext_ref)
|
|
99
|
+
|
|
100
|
+
# download binary file
|
|
101
|
+
binary_url = CycloneDxSupport.get_ext_ref_binary_url(component)
|
|
102
|
+
if binary_url:
|
|
103
|
+
result = BomDownloadSources.download_source_file(binary_url._uri, target_folder, is_binary=True)
|
|
104
|
+
else:
|
|
105
|
+
result = None
|
|
106
|
+
print_yellow(" No binary URL specified!")
|
|
107
|
+
|
|
108
|
+
if result is not None:
|
|
109
|
+
(path, sha1) = result
|
|
110
|
+
# move file to appropriate location
|
|
111
|
+
filename = pathlib.Path(path).name
|
|
112
|
+
targetsha = os.path.join(target_folder, "binaries", sha1)
|
|
113
|
+
os.makedirs(targetsha, exist_ok=True)
|
|
114
|
+
target = os.path.join(targetsha, filename)
|
|
115
|
+
shutil.move(path, target)
|
|
116
|
+
|
|
117
|
+
# update SBOM
|
|
118
|
+
new = False
|
|
119
|
+
ext_ref = CycloneDxSupport.get_ext_ref(
|
|
120
|
+
component, ExternalReferenceType.DISTRIBUTION, CaPyCliBom.BINARY_FILE_COMMENT)
|
|
121
|
+
file_uri = posixpath.join("binaries", sha1, filename)
|
|
122
|
+
if not file_uri.startswith("file://"):
|
|
123
|
+
file_uri = "file:///" + file_uri
|
|
124
|
+
if not ext_ref:
|
|
125
|
+
ext_ref = ExternalReference(
|
|
126
|
+
type=ExternalReferenceType.DISTRIBUTION,
|
|
127
|
+
comment=CaPyCliBom.BINARY_FILE_COMMENT,
|
|
128
|
+
url=XsUri(file_uri))
|
|
129
|
+
new = True
|
|
130
|
+
else:
|
|
131
|
+
ext_ref.url = XsUri(file_uri)
|
|
132
|
+
ext_ref.hashes.add(HashType(
|
|
133
|
+
alg=HashAlgorithm.SHA_1,
|
|
134
|
+
content=sha1))
|
|
135
|
+
if new:
|
|
136
|
+
component.external_references.add(ext_ref)
|
|
137
|
+
|
|
138
|
+
def run(self, args: Any) -> None:
|
|
139
|
+
"""Main method
|
|
140
|
+
|
|
141
|
+
@params:
|
|
142
|
+
args - command line arguments
|
|
143
|
+
"""
|
|
144
|
+
if args.debug:
|
|
145
|
+
global LOG
|
|
146
|
+
LOG = capycli.get_logger(__name__)
|
|
147
|
+
else:
|
|
148
|
+
# suppress (debug) log output from requests and urllib
|
|
149
|
+
logging.getLogger("requests").setLevel(logging.WARNING)
|
|
150
|
+
logging.getLogger("urllib3").setLevel(logging.WARNING)
|
|
151
|
+
logging.getLogger("urllib3.connectionpool").setLevel(logging.WARNING)
|
|
152
|
+
|
|
153
|
+
print_text(
|
|
154
|
+
"\n" + capycli.get_app_signature() +
|
|
155
|
+
" - create a single zip archive that contains the SBOM and all source and binary files\n")
|
|
156
|
+
|
|
157
|
+
if args.help:
|
|
158
|
+
print("usage: capycli bom bompackage -i bom.json")
|
|
159
|
+
print("")
|
|
160
|
+
print("optional arguments:")
|
|
161
|
+
print(" -h, --help show this help message and exit")
|
|
162
|
+
print(" -i INPUTFILE, input SBOM file to read from (JSON)")
|
|
163
|
+
print(" -o OUTPUT ARCHIVE, path of the output zip archive")
|
|
164
|
+
print(" -v be verbose")
|
|
165
|
+
return
|
|
166
|
+
|
|
167
|
+
if not args.inputfile:
|
|
168
|
+
print_red("No input file specified!")
|
|
169
|
+
sys.exit(ResultCode.RESULT_COMMAND_ERROR)
|
|
170
|
+
|
|
171
|
+
if not os.path.isfile(args.inputfile):
|
|
172
|
+
print_red("Input file not found!")
|
|
173
|
+
sys.exit(ResultCode.RESULT_FILE_NOT_FOUND)
|
|
174
|
+
|
|
175
|
+
if not args.outputfile:
|
|
176
|
+
print_red("No output file specified!")
|
|
177
|
+
sys.exit(ResultCode.RESULT_COMMAND_ERROR)
|
|
178
|
+
|
|
179
|
+
print_text("Loading SBOM file " + args.inputfile)
|
|
180
|
+
try:
|
|
181
|
+
bom = CaPyCliBom.read_sbom(args.inputfile)
|
|
182
|
+
except Exception as ex:
|
|
183
|
+
print_red("Error reading input SBOM file: " + repr(ex))
|
|
184
|
+
sys.exit(ResultCode.RESULT_ERROR_READING_BOM)
|
|
185
|
+
|
|
186
|
+
if args.verbose:
|
|
187
|
+
print_text(" " + str(len(bom.components)) + "components read from SBOM file")
|
|
188
|
+
|
|
189
|
+
temp_dir = tempfile.TemporaryDirectory(prefix="capycli_bom_pkg_")
|
|
190
|
+
target_folder = temp_dir.name
|
|
191
|
+
|
|
192
|
+
pp = pathlib.Path(args.outputfile)
|
|
193
|
+
if pp.suffix.lower() != ".zip":
|
|
194
|
+
print_yellow("Warning: bom package file should have .zip extension")
|
|
195
|
+
args.bom_package = args.outputfile + ".zip"
|
|
196
|
+
|
|
197
|
+
print_text("\nDownloading files to folder " + target_folder + " ...")
|
|
198
|
+
|
|
199
|
+
self.download_files(bom, target_folder)
|
|
200
|
+
|
|
201
|
+
print_text("\nCreating BOM package " + args.outputfile)
|
|
202
|
+
try:
|
|
203
|
+
# add SBOM to temp folder
|
|
204
|
+
sbom_file = os.path.join(target_folder, "sbom.cdx.json")
|
|
205
|
+
SbomWriter.write_to_json(bom, sbom_file, True)
|
|
206
|
+
shutil.make_archive(
|
|
207
|
+
base_name=args.outputfile.rstrip(".zip"),
|
|
208
|
+
format="zip",
|
|
209
|
+
root_dir=target_folder)
|
|
210
|
+
temp_dir.cleanup()
|
|
211
|
+
except Exception as ex:
|
|
212
|
+
print_red("Error creating BOM package: " + repr(ex))
|
|
213
|
+
sys.exit(ResultCode.RESULT_ERROR_WRITING_BOM)
|
|
214
|
+
|
|
215
|
+
print("\n")
|
|
@@ -11,9 +11,7 @@ import logging
|
|
|
11
11
|
import os
|
|
12
12
|
import pathlib
|
|
13
13
|
import re
|
|
14
|
-
import shutil
|
|
15
14
|
import sys
|
|
16
|
-
import tempfile
|
|
17
15
|
from typing import Any, Optional, Tuple
|
|
18
16
|
from urllib.parse import urlparse
|
|
19
17
|
|
|
@@ -36,7 +34,8 @@ class BomDownloadSources(capycli.common.script_base.ScriptBase):
|
|
|
36
34
|
Download source files from the URL specified in the SBOM.
|
|
37
35
|
"""
|
|
38
36
|
|
|
39
|
-
|
|
37
|
+
@staticmethod
|
|
38
|
+
def get_filename_from_cd(cd: str) -> str:
|
|
40
39
|
"""
|
|
41
40
|
Get filename from content-disposition.
|
|
42
41
|
"""
|
|
@@ -57,18 +56,20 @@ class BomDownloadSources(capycli.common.script_base.ScriptBase):
|
|
|
57
56
|
_, extension = os.path.splitext(filename)
|
|
58
57
|
return extension in good_extensions
|
|
59
58
|
|
|
60
|
-
|
|
59
|
+
@staticmethod
|
|
60
|
+
def download_source_file(url: str, source_folder: str, is_binary: bool = False) -> Optional[Tuple[str, str]]:
|
|
61
61
|
"""Download a file from a URL.
|
|
62
62
|
|
|
63
63
|
@params:
|
|
64
64
|
url - Required : url of the file to get uploaded (string)
|
|
65
65
|
source_folder - Required : folder to store the source files (string)
|
|
66
|
+
is_binary - Optional : whether the file is a binary file (boolean, default: False)
|
|
66
67
|
"""
|
|
67
68
|
print_text(" URL = " + url)
|
|
68
69
|
|
|
69
70
|
try:
|
|
70
71
|
response = requests.get(url, allow_redirects=True)
|
|
71
|
-
filename =
|
|
72
|
+
filename = BomDownloadSources.get_filename_from_cd(response.headers.get("content-disposition", ""))
|
|
72
73
|
if not filename:
|
|
73
74
|
filename_ps = urlparse(url)
|
|
74
75
|
if filename_ps:
|
|
@@ -82,7 +83,7 @@ class BomDownloadSources(capycli.common.script_base.ScriptBase):
|
|
|
82
83
|
path = os.path.join(source_folder, filename)
|
|
83
84
|
if (response.status_code == requests.codes["ok"]):
|
|
84
85
|
open(path, "wb").write(response.content)
|
|
85
|
-
if not BomDownloadSources.is_good_source_file(path):
|
|
86
|
+
if not is_binary and not BomDownloadSources.is_good_source_file(path):
|
|
86
87
|
print_yellow(" Downloaded file seems not to be a valid source file!")
|
|
87
88
|
sha1 = hashlib.sha1(response.content).hexdigest()
|
|
88
89
|
return (path, sha1)
|
|
@@ -187,7 +188,6 @@ class BomDownloadSources(capycli.common.script_base.ScriptBase):
|
|
|
187
188
|
print(" -source SOURCE source folder or additional source file")
|
|
188
189
|
print(" -o OUTPUTFILE output file to write to")
|
|
189
190
|
print(" -v be verbose")
|
|
190
|
-
print(" -bp, --bom-package create a single zip archive that contains the SBOM and all source files")
|
|
191
191
|
return
|
|
192
192
|
|
|
193
193
|
if not args.inputfile:
|
|
@@ -209,23 +209,12 @@ class BomDownloadSources(capycli.common.script_base.ScriptBase):
|
|
|
209
209
|
print_text(" " + str(len(bom.components)) + "components read from SBOM file")
|
|
210
210
|
|
|
211
211
|
source_folder = "./"
|
|
212
|
-
cleanup_temp_dir = False
|
|
213
|
-
if args.bom_package and not args.source:
|
|
214
|
-
temp_dir = tempfile.TemporaryDirectory(prefix="capycli_bom_pkg_")
|
|
215
|
-
source_folder = temp_dir.name
|
|
216
|
-
cleanup_temp_dir = True
|
|
217
212
|
if args.source:
|
|
218
213
|
source_folder = args.source
|
|
219
214
|
if (not source_folder) or (not os.path.isdir(source_folder)):
|
|
220
215
|
print_red("Target source code folder does not exist!")
|
|
221
216
|
sys.exit(ResultCode.RESULT_COMMAND_ERROR)
|
|
222
217
|
|
|
223
|
-
if args.bom_package:
|
|
224
|
-
pp = pathlib.Path(args.bom_package)
|
|
225
|
-
if pp.suffix.lower() != ".zip":
|
|
226
|
-
print_yellow("Warning: bom package file should have .zip extension")
|
|
227
|
-
args.bom_package = args.bom_package + ".zip"
|
|
228
|
-
|
|
229
218
|
print_text("\nDownloading source files to folder " + source_folder + " ...")
|
|
230
219
|
|
|
231
220
|
self.download_sources(bom, source_folder)
|
|
@@ -244,21 +233,4 @@ class BomDownloadSources(capycli.common.script_base.ScriptBase):
|
|
|
244
233
|
if args.verbose:
|
|
245
234
|
print_text(" " + str(len(bom.components)) + " components written to SBOM file")
|
|
246
235
|
|
|
247
|
-
if args.bom_package:
|
|
248
|
-
print_text("\nCreating BOM package " + args.bom_package)
|
|
249
|
-
try:
|
|
250
|
-
# add SBOM to temp folder
|
|
251
|
-
sbom_file = os.path.join(source_folder, "sbom.cdx.json")
|
|
252
|
-
self.update_local_path(bom, sbom_file)
|
|
253
|
-
SbomWriter.write_to_json(bom, sbom_file, True)
|
|
254
|
-
shutil.make_archive(
|
|
255
|
-
base_name=args.bom_package.rstrip(".zip"),
|
|
256
|
-
format="zip",
|
|
257
|
-
root_dir=source_folder)
|
|
258
|
-
if cleanup_temp_dir:
|
|
259
|
-
temp_dir.cleanup()
|
|
260
|
-
except Exception as ex:
|
|
261
|
-
print_red("Error creating BOM package: " + repr(ex))
|
|
262
|
-
sys.exit(ResultCode.RESULT_ERROR_WRITING_BOM)
|
|
263
|
-
|
|
264
236
|
print("\n")
|
|
@@ -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
|
#
|
|
@@ -10,6 +10,7 @@ import sys
|
|
|
10
10
|
from typing import Any
|
|
11
11
|
|
|
12
12
|
import capycli.bom.bom_convert
|
|
13
|
+
import capycli.bom.bom_package
|
|
13
14
|
import capycli.bom.bom_validate
|
|
14
15
|
import capycli.bom.check_bom
|
|
15
16
|
import capycli.bom.check_bom_item_status
|
|
@@ -51,6 +52,7 @@ def run_bom_command(args: Any) -> None:
|
|
|
51
52
|
print(" Merge merge two bills of material")
|
|
52
53
|
print(" Findsources determine the source code for SBOM items")
|
|
53
54
|
print(" Validate validate an SBOM")
|
|
55
|
+
print(" BomPackage create a single archive that contains the SBOM and all source and binary files")
|
|
54
56
|
return
|
|
55
57
|
|
|
56
58
|
subcommand = args.command[1].lower()
|
|
@@ -139,5 +141,11 @@ def run_bom_command(args: Any) -> None:
|
|
|
139
141
|
app14.run(args)
|
|
140
142
|
return
|
|
141
143
|
|
|
144
|
+
if subcommand == "bompackage":
|
|
145
|
+
"""Validate an SBOM."""
|
|
146
|
+
app15 = capycli.bom.bom_package.BomPackage()
|
|
147
|
+
app15.run(args)
|
|
148
|
+
return
|
|
149
|
+
|
|
142
150
|
print_red("Unknown sub-command: ")
|
|
143
151
|
sys.exit(ResultCode.RESULT_COMMAND_ERROR)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# -------------------------------------------------------------------------------
|
|
2
|
-
# Copyright (c) 2019-
|
|
2
|
+
# Copyright (c) 2019-2026 Siemens
|
|
3
3
|
# All Rights Reserved.
|
|
4
4
|
# Author: thomas.graf@siemens.com
|
|
5
5
|
#
|
|
@@ -64,11 +64,14 @@ class ShowBom(capycli.common.script_base.ScriptBase):
|
|
|
64
64
|
return
|
|
65
65
|
|
|
66
66
|
for bomitem in bom.components:
|
|
67
|
+
name = bomitem.name
|
|
68
|
+
if bomitem.group:
|
|
69
|
+
name = bomitem.group + "/" + bomitem.name
|
|
67
70
|
if not bomitem.version:
|
|
68
|
-
print_text(" " +
|
|
71
|
+
print_text(" " + name)
|
|
69
72
|
print_yellow(" component version is missing!")
|
|
70
73
|
else:
|
|
71
|
-
print_text(" " +
|
|
74
|
+
print_text(" " + name + ", " + bomitem.version)
|
|
72
75
|
|
|
73
76
|
if self.verbose:
|
|
74
77
|
if bomitem.purl:
|
|
@@ -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
|
#
|
|
@@ -14,6 +14,7 @@ import capycli.dependencies.maven_list
|
|
|
14
14
|
import capycli.dependencies.maven_pom
|
|
15
15
|
import capycli.dependencies.nuget
|
|
16
16
|
import capycli.dependencies.python
|
|
17
|
+
import capycli.dependencies.rust
|
|
17
18
|
from capycli.common.print import print_red
|
|
18
19
|
from capycli.main.result_codes import ResultCode
|
|
19
20
|
|
|
@@ -34,6 +35,7 @@ def run_dependency_command(args: Any) -> None:
|
|
|
34
35
|
print(" Javascript determine dependencies for a JavaScript project")
|
|
35
36
|
print(" MavenPom determine dependencies for a Java/Maven project using the pom.xml file")
|
|
36
37
|
print(" MavenList determine dependencies for a Java/Maven project using a Maven command")
|
|
38
|
+
print(" Rust determine dependencies for a Rust project")
|
|
37
39
|
return
|
|
38
40
|
|
|
39
41
|
subcommand = args.command[1].lower()
|
|
@@ -67,5 +69,11 @@ def run_dependency_command(args: Any) -> None:
|
|
|
67
69
|
app5.run(args)
|
|
68
70
|
return
|
|
69
71
|
|
|
72
|
+
if subcommand == "rust":
|
|
73
|
+
"""Determine Rust components/dependencies for a given project"""
|
|
74
|
+
app6 = capycli.dependencies.rust.GetRustDependencies()
|
|
75
|
+
app6.run(args)
|
|
76
|
+
return
|
|
77
|
+
|
|
70
78
|
print_red("Unknown sub-command: " + subcommand)
|
|
71
79
|
sys.exit(ResultCode.RESULT_COMMAND_ERROR)
|
|
@@ -105,8 +105,9 @@ class GetJavascriptDependencies(capycli.common.dependencies_base.DependenciesBas
|
|
|
105
105
|
if "dev" in dep:
|
|
106
106
|
isdev = dep["dev"]
|
|
107
107
|
|
|
108
|
+
version = dep.get("version", "")
|
|
108
109
|
if isdev:
|
|
109
|
-
|
|
110
|
+
print_yellow("Ignoring dev dependency: " + key + "," + dep["version"])
|
|
110
111
|
continue
|
|
111
112
|
|
|
112
113
|
modified_key = ""
|
|
@@ -115,11 +116,14 @@ class GetJavascriptDependencies(capycli.common.dependencies_base.DependenciesBas
|
|
|
115
116
|
else:
|
|
116
117
|
modified_key = key
|
|
117
118
|
|
|
118
|
-
|
|
119
|
-
|
|
119
|
+
if dep.get("link", ""):
|
|
120
|
+
print_yellow("Ignoring linked dependency: " + modified_key + "," + version)
|
|
121
|
+
continue
|
|
122
|
+
LOG.debug("Checking dependency: " + modified_key + "," + version)
|
|
123
|
+
purl = PackageURL("npm", "", modified_key, version, "", "")
|
|
120
124
|
cxcomp = Component(
|
|
121
125
|
name=modified_key.strip(),
|
|
122
|
-
version=
|
|
126
|
+
version=version.strip(),
|
|
123
127
|
purl=purl,
|
|
124
128
|
bom_ref=purl.to_string())
|
|
125
129
|
|
|
@@ -779,7 +779,8 @@ class GetPythonDependencies(capycli.common.script_base.ScriptBase):
|
|
|
779
779
|
|
|
780
780
|
return sbom
|
|
781
781
|
|
|
782
|
-
|
|
782
|
+
@staticmethod
|
|
783
|
+
def check_meta_data(sbom: Bom, verbose: bool) -> bool:
|
|
783
784
|
"""
|
|
784
785
|
Check whether all required meta-data is available.
|
|
785
786
|
|
|
@@ -790,37 +791,37 @@ class GetPythonDependencies(capycli.common.script_base.ScriptBase):
|
|
|
790
791
|
bool: True if all required meta-data is available; otherwise False.
|
|
791
792
|
"""
|
|
792
793
|
|
|
793
|
-
if
|
|
794
|
+
if verbose:
|
|
794
795
|
print_text("\nChecking meta-data:")
|
|
795
796
|
|
|
796
797
|
result = True
|
|
797
798
|
cxcomp: Component
|
|
798
799
|
for cxcomp in sbom.components:
|
|
799
|
-
if
|
|
800
|
+
if verbose:
|
|
800
801
|
print_text(f" {cxcomp.name}, {cxcomp.version}")
|
|
801
802
|
|
|
802
803
|
if not cxcomp.purl:
|
|
803
804
|
result = False
|
|
804
|
-
if
|
|
805
|
+
if verbose:
|
|
805
806
|
print_yellow(" package-url missing")
|
|
806
807
|
|
|
807
808
|
homepage = CycloneDxSupport.get_ext_ref_website(cxcomp)
|
|
808
809
|
if not homepage:
|
|
809
810
|
result = False
|
|
810
|
-
if
|
|
811
|
+
if verbose:
|
|
811
812
|
print_yellow(" Homepage missing")
|
|
812
813
|
|
|
813
814
|
if not cxcomp.licenses:
|
|
814
|
-
if
|
|
815
|
+
if verbose:
|
|
815
816
|
LOG.debug(" License missing")
|
|
816
817
|
elif len(cxcomp.licenses) == 0:
|
|
817
|
-
if
|
|
818
|
+
if verbose:
|
|
818
819
|
LOG.debug(" License missing")
|
|
819
820
|
|
|
820
821
|
src_url = CycloneDxSupport.get_ext_ref_source_url(cxcomp)
|
|
821
822
|
if not src_url:
|
|
822
823
|
result = False
|
|
823
|
-
if
|
|
824
|
+
if verbose:
|
|
824
825
|
print_yellow(" Source code URL missing")
|
|
825
826
|
|
|
826
827
|
return result
|
|
@@ -884,7 +885,7 @@ class GetPythonDependencies(capycli.common.script_base.ScriptBase):
|
|
|
884
885
|
print_text("Formatting package list...")
|
|
885
886
|
sbom = self.convert_package_list(package_list, args.search_meta_data, args.package_source)
|
|
886
887
|
|
|
887
|
-
|
|
888
|
+
GetPythonDependencies.check_meta_data(sbom, self.verbose)
|
|
888
889
|
|
|
889
890
|
if self.verbose:
|
|
890
891
|
print()
|