Skip to content

Commit 6b621b0

Browse files
committed
Update Attachment structure
Problem: Current attachment handling is not flexible and confusing and makes sharing attachments hard. Solution: Updated attachment structure to be more flexible and to allow easy sharing without any special handling. Signed-off-by: Waldemar Parzonka <[email protected]>
1 parent cd61b6e commit 6b621b0

15 files changed

+196
-126
lines changed

archivist/archivistpublic.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ def get_file(
228228
229229
Args:
230230
url (str): e.g. assets/xxxxxxxxxxxxxxxxxxxxxx
231-
identity (str): e.g. attachments/xxxxxxxxxxxxxxxxxxxxxxxxxxxx`
231+
identity (str): e.g. blobs/xxxxxxxxxxxxxxxxxxxxxxxxxxxx`
232232
fd (file): an iterable representing a file (usually from open())
233233
the file must be opened in binary mode
234234
headers (dict): optional REST headers

archivist/asset.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,14 @@ def primary_image(self) -> str | None:
2121
:class:`Attachment` instance
2222
2323
"""
24+
25+
# try latest API
26+
try:
27+
return self["attributes"]["arc_primary_image"]
28+
except (KeyError, TypeError):
29+
pass
30+
31+
# try old API
2432
try:
2533
attachments = self["attributes"]["arc_attachments"]
2634
except (KeyError, TypeError):

archivist/assets.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,7 @@ def create_if_not_exists(
204204
attachments:
205205
- filename: functests/test_resources/doors/assets/entry-terminal.jpg
206206
content_type: image/jpg
207+
attachment: terminal entry
207208
208209
The 'selector' value is required and will usually specify the 'arc_display_name' as a
209210
secondary key. The keys in 'selector' must exist in the attributes of the asset.
@@ -250,9 +251,15 @@ def create_if_not_exists(
250251

251252
# any attachments ?
252253
if attachments is not None:
253-
data["attributes"]["arc_attachments"] = [
254-
self._archivist.attachments.create(a) for a in attachments
255-
]
254+
for a in attachments:
255+
# attempt to get attachment to use as a key
256+
attachment_key = a.get("attachment", None)
257+
if attachment_key is None:
258+
# failing that create a key from filename or url
259+
attachment_key = self._archivist.attachments.get_default_key(a)
260+
data["attributes"][attachment_key] = self._archivist.attachments.create(
261+
a
262+
)
256263

257264
asset = self.create_from_data(
258265
data=data,

archivist/attachments.py

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
from copy import deepcopy
2929
from io import BytesIO
3030
from logging import getLogger
31+
from os import path
3132
from typing import BinaryIO, Optional, Any
3233

3334
from requests.models import Response
@@ -72,6 +73,18 @@ def __init__(self, archivist_instance: archivist.Archivist):
7273
def __str__(self) -> str:
7374
return f"AttachmentsClient({self._archivist.url})"
7475

76+
def get_default_key(self, data: dict[str, str]) -> str:
77+
"""
78+
Return a key to use if no key was provided
79+
either use filename or url as one of them is required
80+
"""
81+
attachment_key = (
82+
data.get("filename", "")
83+
if data.get("filename", "")
84+
else data.get("url", "")
85+
)
86+
return attachment_key.replace(".", "_")
87+
7588
def create(self, data: dict[str, Any]) -> dict[str, Any]: # pragma: no cover
7689
"""
7790
Create an attachment and return struct suitable for use in an asset
@@ -108,14 +121,17 @@ def create(self, data: dict[str, Any]) -> dict[str, Any]: # pragma: no cover
108121
.. code-block:: yaml
109122
110123
arc_display_name: Telephone
111-
arc_attachment_identity: blobs/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
112-
arc_hash_alg: SHA256
113-
arc_hash_value: xxxxxxxxxxxxxxxxxxxxxxx
124+
arc_blob_identity: blobs/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
125+
arc_blob_hash_alg: SHA256
126+
arc_blob_hash_value: xxxxxxxxxxxxxxxxxxxxxxx
127+
arc_file_name: gdn_front.jpg
114128
115129
"""
116130
result = None
131+
file_part = None
117132
filename = data.get("filename")
118133
if filename is not None:
134+
_, file_part = path.split(filename)
119135
with open(filename, "rb") as fd:
120136
attachment = self.upload(fd, mtype=data.get("content_type"))
121137

@@ -126,11 +142,15 @@ def create(self, data: dict[str, Any]) -> dict[str, Any]: # pragma: no cover
126142
attachment = self.upload(fd, mtype=data.get("content_type"))
127143

128144
result = {
129-
"arc_attachment_identity": attachment["identity"],
130-
"arc_hash_alg": attachment["hash"]["alg"],
131-
"arc_hash_value": attachment["hash"]["value"],
145+
"arc_attribute_type": "arc_attachment",
146+
"arc_blob_identity": attachment["identity"],
147+
"arc_blob_hash_alg": attachment["hash"]["alg"],
148+
"arc_blob_hash_value": attachment["hash"]["value"],
132149
}
133150

151+
if file_part:
152+
result["arc_file_name"] = file_part
153+
134154
display_name = data.get("display_name")
135155
if display_name is not None:
136156
result["arc_display_name"] = display_name
@@ -179,7 +199,7 @@ def download(
179199
fd iterator
180200
181201
Args:
182-
identity (str): attachment identity e.g. attachments/xxxxxxxxxxxxxxxxxxxxxxx
202+
identity (str): attachment identity e.g. blobs/xxxxxxxxxxxxxxxxxxxxxxx
183203
fd (file): opened file descriptor or other file-type sink..
184204
params (dict): e.g. {"allow_insecure": "true"} OR {"strict": "true" }
185205
@@ -202,7 +222,7 @@ def info(
202222
Reads attachment info
203223
204224
Args:
205-
identity (str): attachment identity e.g. attachments/xxxxxxxxxxxxxxxxxxxxxxx
225+
identity (str): attachment identity e.g. blobs/xxxxxxxxxxxxxxxxxxxxxxx
206226
207227
Returns:
208228
REST response

archivist/events.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -360,19 +360,20 @@ def create_from_data(
360360

361361
attachments = data.pop("attachments", None)
362362
if attachments is not None:
363-
result = [self._archivist.attachments.create(a) for a in attachments]
364-
for i, a in enumerate(attachments):
363+
for a in attachments:
364+
result = self._archivist.attachments.create(a)
365365
if a.get("type") == SBOM_RELEASE:
366366
sbom_result = self._archivist.sboms.parse(a)
367367
for k, v in sbom_result.items():
368368
event_attributes[f"sbom_{k}"] = v
369369

370-
event_attributes["sbom_identity"] = result[i][
371-
"arc_attachment_identity"
372-
]
373-
break
370+
event_attributes["sbom_identity"] = result["arc_blob_identity"]
374371

375-
event_attributes["arc_attachments"] = result
372+
attachment_key = a.get("attachment", None)
373+
if attachment_key is None:
374+
# failing that create a key from filename or url
375+
attachment_key = self._archivist.attachments.get_default_key(a)
376+
event_attributes[attachment_key] = result
376377

377378
data["event_attributes"] = event_attributes
378379

archivist/notebooks/Find Asset and Create Attachment.ipynb

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -132,9 +132,9 @@
132132
" blob = arch.attachments.upload(fd)\n",
133133
" attachment = {\n",
134134
" \"arc_display_name\": name,\n",
135-
" \"arc_attachment_identity\": blob[\"identity\"],\n",
136-
" \"arc_hash_value\": blob[\"hash\"][\"value\"],\n",
137-
" \"arc_hash_alg\": blob[\"hash\"][\"alg\"],\n",
135+
" \"arc_blob_identity\": blob[\"identity\"],\n",
136+
" \"arc_blob_hash_value\": blob[\"hash\"][\"value\"],\n",
137+
" \"arc_blob_hash_alg\": blob[\"hash\"][\"alg\"],\n",
138138
" }\n",
139139
" return attachment"
140140
]
@@ -173,15 +173,14 @@
173173
" attachments = upload_attachment(\n",
174174
" arch, \"pexels-andrea-turner-707697.jpeg\", \"arc_primary_image\"\n",
175175
" )\n",
176-
" attachment_list = [attachments]\n",
177176
"\n",
178177
" props = {\"operation\": \"Record\", \"behaviour\": \"RecordEvidence\"}\n",
179178
" attrs = {\n",
180179
" \"arc_description\": \"Attaching an image\",\n",
181180
" \"arc_display_type\": \"Primary image\",\n",
182181
" }\n",
183182
"\n",
184-
" asset_attrs = {\"arc_attachments\": attachment_list}\n",
183+
" asset_attrs = {\"pexels-andrea-turner-707697\": attachments}\n",
185184
"\n",
186185
" return arch.events.create(\n",
187186
" asset[\"identity\"], props=props, attrs=attrs, asset_attrs=asset_attrs\n",

archivist/sboms.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ def create(self, data: dict[str, Any]) -> dict[str, Any]: # pragma: no cover
174174
.. code-block:: yaml
175175
176176
arc_display_name: Acme Generation1 SBOM
177-
arc_attachment_identity: sboms/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
177+
arc_blob_identity: sboms/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
178178
.....
179179
180180
"""

examples/scan_test.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,12 @@ def scan_test(arch, datestring, scanned_expected=False):
6767
"main/functests/test_resources/telephone.jpg"
6868
),
6969
"content_type": "image/jpg",
70+
"attachment": "telephone1",
7071
},
7172
{
7273
"url": "https://secure.eicar.org/eicarcom2.zip",
7374
"content_type": "application/zip",
75+
"attachment": "zipfile",
7476
},
7577
],
7678
},
@@ -82,7 +84,7 @@ def scan_test(arch, datestring, scanned_expected=False):
8284
fails = []
8385

8486
# first attachment should be clean....
85-
attachment_id = asset["attributes"]["arc_attachments"][0]["arc_attachment_identity"]
87+
attachment_id = asset["attributes"]["telephone1"]["arc_blob_identity"]
8688
info = arch.attachments.info(
8789
attachment_id,
8890
asset_or_event_id=asset["identity"],
@@ -100,7 +102,7 @@ def scan_test(arch, datestring, scanned_expected=False):
100102
fails.append("Yesterday's first attachment has not been scanned.")
101103

102104
# second attachment should be bad when scanned....
103-
attachment_id = asset["attributes"]["arc_attachments"][1]["arc_attachment_identity"]
105+
attachment_id = asset["attributes"]["zipfile"]["arc_blob_identity"]
104106
info = arch.attachments.info(
105107
attachment_id,
106108
asset_or_event_id=asset["identity"],

functests/execassets.py

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,12 @@
5959
{
6060
"filename": "functests/test_resources/telephone.jpg",
6161
"content_type": "image/jpg",
62+
"attachment": "telephone",
6263
},
6364
{
6465
"url": "https://secure.eicar.org/eicarcom2.zip",
6566
"content_type": "application/zip",
67+
"attachment": "zipfile",
6668
},
6769
],
6870
}
@@ -271,9 +273,7 @@ def test_asset_create_if_not_exists_with_bad_attachment(self):
271273
LOGGER.debug("existed %s", existed)
272274

273275
# first attachment is ok....
274-
attachment_id = asset["attributes"]["arc_attachments"][0][
275-
"arc_attachment_identity"
276-
]
276+
attachment_id = asset["attributes"]["telephone"]["arc_blob_identity"]
277277
info = self.arch.attachments.info(
278278
attachment_id,
279279
)
@@ -290,9 +290,7 @@ def test_asset_create_if_not_exists_with_bad_attachment(self):
290290
)
291291

292292
# second attachment is bad when scanned....
293-
attachment_id = asset["attributes"]["arc_attachments"][1][
294-
"arc_attachment_identity"
295-
]
293+
attachment_id = asset["attributes"]["zipfile"]["arc_blob_identity"]
296294
info = self.arch.attachments.info(
297295
attachment_id,
298296
)
@@ -327,9 +325,7 @@ def test_asset_create_if_not_exists_with_bad_attachment_assetattachment(self):
327325
LOGGER.debug("existed %s", existed)
328326

329327
# first attachment is ok....
330-
attachment_id = asset["attributes"]["arc_attachments"][0][
331-
"arc_attachment_identity"
332-
]
328+
attachment_id = asset["attributes"]["telephone"]["arc_blob_identity"]
333329
info = self.arch.assetattachments.info(
334330
asset["identity"],
335331
attachment_id,
@@ -347,9 +343,7 @@ def test_asset_create_if_not_exists_with_bad_attachment_assetattachment(self):
347343
)
348344

349345
# second attachment is bad when scanned....
350-
attachment_id = asset["attributes"]["arc_attachments"][1][
351-
"arc_attachment_identity"
352-
]
346+
attachment_id = asset["attributes"]["zipfile"]["arc_blob_identity"]
353347
info = self.arch.assetattachments.info(
354348
asset["identity"],
355349
attachment_id,

functests/execpublicassets.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,12 @@
5959
{
6060
"filename": "functests/test_resources/telephone.jpg",
6161
"content_type": "image/jpg",
62+
"attachment": "telephone",
6263
},
6364
{
6465
"url": "https://secure.eicar.org/eicarcom2.zip",
6566
"content_type": "application/zip",
67+
"attachment": "zipfile",
6668
},
6769
],
6870
"public": True,
@@ -231,9 +233,7 @@ def test_asset_create_if_not_exists_with_bad_attachment_assetattachment(self):
231233

232234
asset_id = asset["identity"]
233235
# first attachment is ok....
234-
attachment_id = asset["attributes"]["arc_attachments"][0][
235-
"arc_attachment_identity"
236-
]
236+
attachment_id = asset["attributes"]["telephone"]["arc_blob_identity"]
237237
public_asset_id = self.arch.assets.publicurl(asset_id)
238238
LOGGER.debug("public asset id %s", public_asset_id)
239239
public = self.arch.Public
@@ -252,9 +252,7 @@ def test_asset_create_if_not_exists_with_bad_attachment_assetattachment(self):
252252
)
253253

254254
# second attachment is bad when scanned....
255-
attachment_id = asset["attributes"]["arc_attachments"][1][
256-
"arc_attachment_identity"
257-
]
255+
attachment_id = asset["attributes"]["zipfile"]["arc_blob_identity"]
258256
info = public.assetattachments.info(public_asset_id, attachment_id)
259257
LOGGER.debug("info attachment1 %s", json_dumps(info, indent=4))
260258
timestamp = info["scanned_timestamp"]

0 commit comments

Comments
 (0)