-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathartefacts.py
134 lines (106 loc) · 4.45 KB
/
artefacts.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
import json
import zlib
import falcon
import gci.componentmodel as cm
import oci.model
class ArtefactBlob:
def __init__(
self,
component_descriptor_lookup,
oci_client,
):
self.component_descriptor_lookup = component_descriptor_lookup
self.oci_client = oci_client
def on_get(self, req: falcon.Request, resp: falcon.Response):
'''
returns a requested artefact (from a OCM Component) as an octet-stream. This route is
limited to artefacts with `localBlob` as access-type.
required query-parameters:
component: ocm-component-id (<name>:<version>)
artefact: has two forms:
1. str - interpreted as `name` attribute
2. json (object) - str-to-str mapping for attributes
optional query-parameters:
ocm_repository: ocm-repository-url
unzip: bool, defaults to true; if true, and artefact's access is gzipped, returned
content will be unzipped (for convenience)
If artefact is not specified unambiguously, the first match will be used.
'''
component_id = req.get_param(
'component',
required=True,
)
if component_id.count(':') != 1:
raise falcon.HTTPBadRequest(title='malformed component-id')
artefact = req.get_param(
'artefact',
required=True,
).strip()
if artefact.startswith('{'):
artefact = json.loads(artefact)
# special-handling for name/version (should refactor in gci/componentmodel)
artefact_name = artefact.pop('name', None)
artefact_version = artefact.pop('version', None)
elif artefact.startswith('['):
raise falcon.HTTPBadRequest(title='bad artefact: either name or json-object is allowed')
else:
artefact_name = artefact
artefact = {}
artefact_version = None
ocm_repository = req.get_param('ocm_repository')
unzip = req.get_param_as_bool('unzip', default=True)
try:
component = self.component_descriptor_lookup(
component_id,
ocm_repository,
).component
except oci.model.OciImageNotFoundException:
raise falcon.HTTPBadRequest(title=f'did not find {component_id=}')
def matches(a: cm.Artifact):
if artefact_name and artefact_name != a.name:
return False
if artefact_version and artefact_version != a.version:
return False
for attr, value in artefact.items():
if a.extraIdentity.get(attr) != value:
return False
return True
for a in component.iter_artefacts():
if matches(a):
break
else:
raise falcon.HTTPBadRequest('did not find requested artefact')
artefact = a
access = artefact.access
if not isinstance(access, cm.LocalBlobAccess):
raise falcon.HTTPBadRequest(
f'{artefact.name=} has {access.type=}; only localBlobAccess is supported',
)
access: cm.LocalBlobAccess
if access.globalAccess:
digest = access.globalAccess.digest
size = access.globalAccess.size
else:
digest = access.localReference
size = access.size
blob = self.oci_client.blob(
image_reference=component.current_ocm_repo.component_oci_ref(component),
digest=digest,
absent_ok=True,
)
fname = f'{component.name}_{component.version}_{artefact.name}'
# required to allow download from delivery-dashboard (CORS):
# https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#attributes
resp.set_header('Content-Disposition', f'attachment; filename="{fname}"')
if unzip and access.mediaType == 'application/gzip':
def iter_uncompressed():
decompressor = zlib.decompressobj(wbits=31)
for chunk in blob.iter_content(chunk_size=4096):
yield decompressor.decompress(chunk)
yield decompressor.flush()
resp.content_type = artefact.type
resp.stream = iter_uncompressed()
return
resp.content_type = access.mediaType
resp.content_length = size
resp.stream = blob.iter_content(chunk_size=4096)