Skip to content

Commit 49f551a

Browse files
committed
feat(bom downloadAttachments): read attachment id from control file
1 parent ddfa863 commit 49f551a

File tree

3 files changed

+81
-138
lines changed

3 files changed

+81
-138
lines changed

capycli/bom/download_attachments.py

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from capycli.common.capycli_bom_support import CaPyCliBom, CycloneDxSupport, SbomWriter
2020
from capycli.common.print import print_red, print_text, print_yellow
2121
from capycli.common.script_support import ScriptSupport
22+
from capycli.common.json_support import load_json_file
2223
from capycli.main.result_codes import ResultCode
2324

2425
LOG = capycli.get_logger(__name__)
@@ -29,7 +30,7 @@ class BomDownloadAttachments(capycli.common.script_base.ScriptBase):
2930
Download SW360 attachments as specified in the SBOM.
3031
"""
3132

32-
def download_attachments(self, sbom: Bom, source_folder: str, bompath: str = None,
33+
def download_attachments(self, sbom: Bom, control_components: list, source_folder: str, bompath: str = None,
3334
attachment_types: Tuple[str] = ("COMPONENT_LICENSE_INFO_XML", "CLEARING_REPORT")) -> Bom:
3435

3536
for component in sbom.components:
@@ -46,27 +47,24 @@ def download_attachments(self, sbom: Bom, source_folder: str, bompath: str = Non
4647
if not found:
4748
continue
4849

49-
attachment_id = ext_ref.comment.split(", sw360Id: ")
50-
if len(attachment_id) != 2:
51-
print_red(" No sw360Id for attachment!")
52-
continue
53-
attachment_id = attachment_id[1]
54-
5550
release_id = CycloneDxSupport.get_property_value(component, CycloneDxSupport.CDX_PROP_SW360ID)
5651
if not release_id:
5752
print_red(" No sw360Id for release!")
5853
continue
59-
print(" ", ext_ref.url, release_id, attachment_id)
6054
filename = os.path.join(source_folder, ext_ref.url)
6155

56+
details = [e for e in control_components
57+
if e["Sw360Id"] == release_id and (
58+
e.get("CliFile", "") == ext_ref.url
59+
or e.get("ReportFile", "") == ext_ref.url)]
60+
if len(details) != 1:
61+
print_red(" ERROR: Found", len(details), "entries for attachment",
62+
ext_ref.url, "of", item_name, "in control file!")
63+
continue
64+
attachment_id = details[0]["Sw360AttachmentId"]
65+
6266
print_text(" Downloading file " + filename)
6367
try:
64-
at_info = self.client.get_attachment(attachment_id)
65-
at_info = {k: v for k, v in at_info.items()
66-
if k.startswith("check")
67-
or k.startswith("created")}
68-
print(at_info)
69-
7068
self.client.download_release_attachment(filename, release_id, attachment_id)
7169
ext_ref.url = filename
7270
try:
@@ -104,6 +102,7 @@ def run(self, args):
104102
print("optional arguments:")
105103
print(" -h, --help show this help message and exit")
106104
print(" -i INPUTFILE, input SBOM to read from, e.g. created by \"project CreateBom\"")
105+
print(" -ct CONTROLFILE, control file to read from as created by \"project CreateBom\"")
107106
print(" -source SOURCE source folder or additional source file")
108107
print(" -o OUTPUTFILE output file to write to")
109108
print(" -v be verbose")
@@ -113,6 +112,10 @@ def run(self, args):
113112
print_red("No input file specified!")
114113
sys.exit(ResultCode.RESULT_COMMAND_ERROR)
115114

115+
if not args.controlfile:
116+
print_red("No control file specified!")
117+
sys.exit(ResultCode.RESULT_COMMAND_ERROR)
118+
116119
if not os.path.isfile(args.inputfile):
117120
print_red("Input file not found!")
118121
sys.exit(ResultCode.RESULT_FILE_NOT_FOUND)
@@ -127,6 +130,16 @@ def run(self, args):
127130
if args.verbose:
128131
print_text(" " + str(len(bom.components)) + "components read from SBOM file")
129132

133+
print_text("Loading control file " + args.controlfile)
134+
try:
135+
control = load_json_file(args.controlfile)
136+
except Exception as ex:
137+
print_red("JSON error reading control file: " + repr(ex))
138+
sys.exit(ResultCode.RESULT_ERROR_READING_BOM)
139+
if "Components" not in control:
140+
print_red("missing Components in control file")
141+
sys.exit(ResultCode.RESULT_ERROR_READING_BOM)
142+
130143
source_folder = "./"
131144
if args.source:
132145
source_folder = args.source
@@ -144,7 +157,7 @@ def run(self, args):
144157

145158
print_text("Downloading source files to folder " + source_folder + " ...")
146159

147-
self.download_attachments(bom, source_folder, os.path.dirname(args.outputfile))
160+
self.download_attachments(bom, control["Components"], source_folder, os.path.dirname(args.outputfile))
148161

149162
if args.outputfile:
150163
print_text("Updating path information")
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"ProjectName": "CaPyCLI, 2.0.0-dev1",
3+
"Components": [
4+
{
5+
"ComponentName": "certifi 2022.12.7",
6+
"Sw360Id": "ae8c7ed",
7+
"Sw360AttachmentId": "794446",
8+
"CreatedBy": "[email protected]",
9+
"CreatedTeam": "AA",
10+
"CreatedOn": "2020-10-23",
11+
"CheckStatus": "ACCEPTED",
12+
"CheckedBy": "[email protected]",
13+
"CheckedTeam": "BB",
14+
"CheckedOn": "2020-10-30",
15+
"CliFile": "CLIXML_certifi-2022.12.7.xml"
16+
},
17+
{
18+
"ComponentName": "certifi 2022.12.7",
19+
"Sw360Id": "ae8c7ed",
20+
"Sw360AttachmentId": "63b368",
21+
"ReportFile": "certifi-2022.12.7_clearing_report.docx"
22+
}
23+
]
24+
}

tests/test_bom_downloadattachments.py

Lines changed: 29 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,16 @@
1111

1212
import responses
1313

14-
from capycli.common.capycli_bom_support import CaPyCliBom, CycloneDxSupport
14+
from capycli.common.capycli_bom_support import CaPyCliBom
15+
from capycli.common.json_support import load_json_file
1516
from capycli.bom.download_attachments import BomDownloadAttachments
1617
from capycli.main.result_codes import ResultCode
17-
from cyclonedx.model import ExternalReferenceType, HashAlgorithm
1818
from tests.test_base import AppArguments, TestBase
1919

2020

2121
class TestBomDownloadAttachments(TestBase):
2222
INPUTFILE = "sbom_for_download.json"
23+
CONTROLFILE = "sbom_for_download-control.json"
2324
INPUTERROR = "plaintext.txt"
2425
OUTPUTFILE = "output.json"
2526

@@ -69,6 +70,8 @@ def test_file_not_found(self) -> None:
6970
args.command.append("bom")
7071
args.command.append("downloadattachments")
7172
args.inputfile = "DOESNOTEXIST"
73+
args.controlfile = os.path.join(os.path.dirname(__file__),
74+
"fixtures", TestBomDownloadAttachments.CONTROLFILE)
7275

7376
sut.run(args)
7477
self.assertTrue(False, "Failed to report missing file")
@@ -85,6 +88,8 @@ def test_error_loading_file(self) -> None:
8588
args.command.append("bom")
8689
args.command.append("downloadattachments")
8790
args.inputfile = os.path.join(os.path.dirname(__file__), "fixtures", TestBomDownloadAttachments.INPUTERROR)
91+
args.controlfile = os.path.join(os.path.dirname(__file__),
92+
"fixtures", TestBomDownloadAttachments.CONTROLFILE)
8893

8994
sut.run(args)
9095
self.assertTrue(False, "Failed to report invalid file")
@@ -103,6 +108,8 @@ def test_source_folder_does_not_exist(self) -> None:
103108
args.command.append("downloadattachments")
104109

105110
args.inputfile = os.path.join(os.path.dirname(__file__), "fixtures", TestBomDownloadAttachments.INPUTFILE)
111+
args.controlfile = os.path.join(os.path.dirname(__file__),
112+
"fixtures", TestBomDownloadAttachments.CONTROLFILE)
106113
args.source = "XXX"
107114

108115
sut.run(args)
@@ -113,32 +120,10 @@ def test_source_folder_does_not_exist(self) -> None:
113120
@responses.activate
114121
def test_simple_bom(self) -> None:
115122
bom = os.path.join(os.path.dirname(__file__), "fixtures", TestBomDownloadAttachments.INPUTFILE)
116-
bom = CaPyCliBom.read_sbom(bom)
123+
controlfile = os.path.join(os.path.dirname(__file__), "fixtures", TestBomDownloadAttachments.CONTROLFILE)
117124

118-
# attachment info - CLI
119-
responses.add(
120-
method=responses.GET,
121-
url=self.MYURL + "resource/api/attachments/794446",
122-
body="""
123-
{
124-
"filename": "CLIXML_certifi-2022.12.7.xml",
125-
"sha1": "3cd24769fa3da4af74d0118433619a130da091b0",
126-
"attachmentType": "COMPONENT_LICENSE_INFO_XML",
127-
"createdBy": "[email protected]",
128-
"createdTeam": "AA",
129-
"createdComment": "comment1",
130-
"createdOn": "2020-10-08",
131-
"checkStatus": "NOTCHECKED",
132-
"_links": {
133-
"self": {
134-
"href": "https://my.server.com/resource/api/attachments/794446"
135-
}
136-
}
137-
}""",
138-
status=200,
139-
content_type="application/json",
140-
adding_headers={"Authorization": "Token " + self.MYTOKEN},
141-
)
125+
bom = CaPyCliBom.read_sbom(bom)
126+
controlfile = load_json_file(controlfile)
142127

143128
# get attachment - CLI
144129
cli_file = self.get_cli_file_mit()
@@ -150,35 +135,6 @@ def test_simple_bom(self) -> None:
150135
content_type="application/text",
151136
adding_headers={"Authorization": "Token " + self.MYTOKEN},
152137
)
153-
154-
# attachment info - report
155-
responses.add(
156-
method=responses.GET,
157-
url=self.MYURL + "resource/api/attachments/63b368",
158-
body="""
159-
{
160-
"filename": "certifi-2022.12.7_clearing_report.docx",
161-
"sha1": "3cd24769fa3da4af74d0118433619a130da091b0",
162-
"attachmentType": "CLEARING_REPORT",
163-
"createdBy": "[email protected]",
164-
"createdTeam": "BB",
165-
"createdComment": "comment3",
166-
"createdOn": "2020-10-08",
167-
"checkedBy": "[email protected]",
168-
"checkedOn" : "2021-01-18",
169-
"checkedComment": "comment4",
170-
"checkStatus": "ACCEPTED",
171-
"_links": {
172-
"self": {
173-
"href": "https://my.server.com/resource/api/attachments/63b368"
174-
}
175-
}
176-
}""",
177-
status=200,
178-
content_type="application/json",
179-
adding_headers={"Authorization": "Token " + self.MYTOKEN},
180-
)
181-
182138
# get attachment - report
183139
responses.add(
184140
method=responses.GET,
@@ -191,7 +147,7 @@ def test_simple_bom(self) -> None:
191147

192148
with tempfile.TemporaryDirectory() as tmpdirname:
193149
try:
194-
bom = self.app.download_attachments(bom, tmpdirname)
150+
bom = self.app.download_attachments(bom, controlfile["Components"], tmpdirname)
195151
resultfile = os.path.join(tmpdirname, "CLIXML_certifi-2022.12.7.xml")
196152
self.assertEqual(bom.components[0].external_references[5].url, resultfile)
197153
self.assertTrue(os.path.isfile(resultfile), "CLI file missing")
@@ -211,25 +167,8 @@ def test_simple_bom_relpath(self) -> None:
211167
bom = os.path.join(os.path.dirname(__file__), "fixtures", TestBomDownloadAttachments.INPUTFILE)
212168
bom = CaPyCliBom.read_sbom(bom)
213169

214-
# attachment info - CLI
215-
responses.add(
216-
method=responses.GET,
217-
url=self.MYURL + "resource/api/attachments/794446",
218-
body="""
219-
{
220-
"filename": "CLIXML_certifi-2022.12.7.xml",
221-
"sha1": "3cd24769fa3da4af74d0118433619a130da091b0",
222-
"attachmentType": "COMPONENT_LICENSE_INFO_XML",
223-
"_links": {
224-
"self": {
225-
"href": "https://my.server.com/resource/api/attachments/794446"
226-
}
227-
}
228-
}""",
229-
status=200,
230-
content_type="application/json",
231-
adding_headers={"Authorization": "Token " + self.MYTOKEN},
232-
)
170+
controlfile = os.path.join(os.path.dirname(__file__), "fixtures", TestBomDownloadAttachments.CONTROLFILE)
171+
controlfile = load_json_file(controlfile)
233172

234173
# get attachment - CLI
235174
cli_file = self.get_cli_file_mit()
@@ -244,7 +183,8 @@ def test_simple_bom_relpath(self) -> None:
244183

245184
with tempfile.TemporaryDirectory() as tmpdirname:
246185
try:
247-
bom = self.app.download_attachments(bom, tmpdirname, tmpdirname, ("COMPONENT_LICENSE_INFO_XML",))
186+
bom = self.app.download_attachments(bom, controlfile["Components"],
187+
tmpdirname, tmpdirname, ("COMPONENT_LICENSE_INFO_XML",))
248188
resultfile = os.path.join(tmpdirname, "CLIXML_certifi-2022.12.7.xml")
249189
self.assertEqual(bom.components[0].external_references[5].url,
250190
"file://CLIXML_certifi-2022.12.7.xml")
@@ -263,59 +203,29 @@ def test_simple_bom_download_errors(self) -> None:
263203
bom = os.path.join(os.path.dirname(__file__), "fixtures", TestBomDownloadAttachments.INPUTFILE)
264204
bom = CaPyCliBom.read_sbom(bom)
265205

266-
# attachment info - CLI, ok
267-
responses.add(
268-
method=responses.GET,
269-
url=self.MYURL + "resource/api/attachments/794446",
270-
body="""
271-
{
272-
"filename": "CLIXML_certifi-2022.12.7.xml",
273-
"sha1": "3cd24769fa3da4af74d0118433619a130da091b0",
274-
"attachmentType": "COMPONENT_LICENSE_INFO_XML",
275-
"_links": {
276-
"self": {
277-
"href": "https://my.server.com/resource/api/attachments/794446"
278-
}
279-
}
280-
}""",
281-
status=200,
282-
content_type="application/json",
283-
adding_headers={"Authorization": "Token " + self.MYTOKEN},
284-
)
206+
controlfile = os.path.join(os.path.dirname(__file__), "fixtures", TestBomDownloadAttachments.CONTROLFILE)
207+
controlfile = load_json_file(controlfile)
285208

286209
# get attachment - CLI, error
287210
responses.add(
288211
method=responses.GET,
289212
url=self.MYURL + "resource/api/releases/ae8c7ed/attachments/794446",
290-
body="cli_file",
291213
status=500,
292214
content_type="application/text",
293215
adding_headers={"Authorization": "Token " + self.MYTOKEN},
294216
)
295-
296-
# attachment info - report, error
217+
# get attachment - CLI, error
297218
responses.add(
298219
method=responses.GET,
299-
url=self.MYURL + "resource/api/attachments/63b368",
300-
body="""
301-
{
302-
"filename": "certifi-2022.12.7_clearing_report.docx",
303-
"sha1": "3cd24769fa3da4af74d0118433619a130da091b0",
304-
"attachmentType": "CLEARING_REPORT",
305-
"_links": {
306-
"self": {
307-
"href": "https://my.server.com/resource/api/attachments/63b368"
308-
}
309-
}
310-
}""",
311-
status=404,
312-
content_type="application/json",
220+
url=self.MYURL + "resource/api/releases/ae8c7ed/attachments/63b368",
221+
status=403,
222+
content_type="application/text",
313223
adding_headers={"Authorization": "Token " + self.MYTOKEN},
314224
)
315225

316226
with tempfile.TemporaryDirectory() as tmpdirname:
317227
try:
318-
bom = self.app.download_attachments(bom, tmpdirname)
228+
bom = self.app.download_attachments(bom, controlfile["Components"], tmpdirname)
319229
resultfile = os.path.join(tmpdirname, "CLIXML_certifi-2022.12.7.xml")
320230
self.assertFalse(os.path.isfile(resultfile), "CLI created despite HTTP 500")
321231

@@ -335,8 +245,8 @@ def test_simple_bom_no_release_id(self) -> None:
335245
bom.components[0].properties = []
336246
with tempfile.TemporaryDirectory() as tmpdirname:
337247
try:
338-
err = self.capture_stdout(self.app.download_attachments, bom, tmpdirname)
339-
assert "No sw360Id for release" in err
248+
err = self.capture_stdout(self.app.download_attachments, bom, [], tmpdirname)
249+
self.assertIn("No sw360Id for release", err)
340250

341251
return
342252
except Exception as e: # noqa
@@ -346,18 +256,14 @@ def test_simple_bom_no_release_id(self) -> None:
346256
self.assertTrue(False, "Error: we must never arrive here")
347257

348258
@responses.activate
349-
def test_simple_bom_no_attachment_id(self) -> None:
259+
def test_simple_bom_no_ctrl_file_entry(self) -> None:
350260
bom = os.path.join(os.path.dirname(__file__), "fixtures", TestBomDownloadAttachments.INPUTFILE)
351261
bom = CaPyCliBom.read_sbom(bom)
352-
bom.components[0].external_references = []
353-
CycloneDxSupport.set_ext_ref(bom.components[0], ExternalReferenceType.OTHER,
354-
CaPyCliBom.CLI_FILE_COMMENT, "CLIXML_foo.xml",
355-
HashAlgorithm.SHA_1, "123")
356262

357263
with tempfile.TemporaryDirectory() as tmpdirname:
358264
try:
359-
err = self.capture_stdout(self.app.download_attachments, bom, tmpdirname)
360-
assert "No sw360Id for attachment" in err
265+
err = self.capture_stdout(self.app.download_attachments, bom, [], tmpdirname)
266+
assert "Found 0 entries for attachment CLIXML_certifi-2022.12.7.xml" in err
361267

362268
return
363269
except Exception as e: # noqa

0 commit comments

Comments
 (0)