Skip to content

Commit b187df4

Browse files
committed
Add s3 upload storage method
Signed-off-by: Mike Perez <thingee@gmail.com>
1 parent 3ed5cdf commit b187df4

File tree

5 files changed

+66
-29
lines changed

5 files changed

+66
-29
lines changed

README.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,18 @@ the service as follows::
4141
api_key = 'secret'
4242

4343

44+
storage_method
45+
^^^^^^^^^^^^^^
46+
The ``storage_method`` is a require configuration item, it defines where the
47+
binaries should be stored. The two available method values are ``local`` and
48+
``s3``.
49+
50+
s3_bucket
51+
^^^^^^^^^
52+
The ``s3_bucket`` is required if the ``storage_method`` configuration is set to
53+
``s3``. This defines which bucket the binaries should be stored to.
54+
55+
4456
Self-discovery
4557
--------------
4658
The API provides informational JSON at every step of the URL about what is

chacra/controllers/binaries/archs.py

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
import hashlib
12
import logging
23
import os
4+
import boto3
5+
from botocore.exceptions import ClientError
36
import pecan
47
from pecan import response
58
from pecan.secure import secure
@@ -26,6 +29,7 @@ def __init__(self, arch):
2629
self.distro_version = request.context['distro_version']
2730
self.ref = request.context['ref']
2831
self.sha1 = request.context['sha1']
32+
self.checksum = None
2933
request.context['arch'] = self.arch
3034

3135
@expose(generic=True, template='json')
@@ -89,7 +93,7 @@ def index_post(self):
8993
if request.POST.get('force', False) is False:
9094
error('/errors/invalid', 'resource already exists and "force" key was not used')
9195

92-
full_path = self.save_file(file_obj)
96+
full_path, size = self.save_file(file_obj)
9397

9498
if self.binary is None:
9599
path = full_path
@@ -102,14 +106,21 @@ def index_post(self):
102106
self.binary = Binary(
103107
self.binary_name, self.project, arch=arch,
104108
distro=distro, distro_version=distro_version,
105-
ref=ref, sha1=sha1, path=path, size=os.path.getsize(path)
109+
ref=ref, sha1=sha1, path=path, size=size,
110+
checksum=self.checksum
106111
)
107112
else:
108113
self.binary.path = full_path
114+
self.binary.checksum = self.checksum
109115

110116
# check if this binary is interesting for other configured projects,
111117
# and if so, then mark those other repos so that they can be re-built
112118
self.mark_related_repos()
119+
120+
# Remove the local file after S3 upload
121+
if pecan.conf.storage_method == 's3':
122+
os.remove(full_path)
123+
113124
return dict()
114125

115126
def mark_related_repos(self):
@@ -175,8 +186,41 @@ def save_file(self, file_obj):
175186
for chunk in file_iterable:
176187
f.write(chunk)
177188

189+
self.checksum = self.generate_checksum(destination)
190+
191+
if pecan.conf.storage_method == 's3':
192+
bucket = pecan.conf.bucket
193+
object_destination = os.path.relpath(destination, pecan.conf.binary_root)
194+
195+
s3_client = boto3.client('s3')
196+
try:
197+
with open(destination, 'rb') as f:
198+
s3_client.put_object(Body=f,
199+
Bucket=bucket,
200+
Key=object_destination,
201+
ChecksumAlgorithm='sha256',
202+
ChecksumSHA256=self.checksum
203+
)
204+
except ClientError as e:
205+
error('/errors/error/', 'file object upload to S3 failed with error %s' % e)
206+
207+
size = os.path.getsize(destination)
208+
178209
# return the full path to the saved object:
179-
return destination
210+
return destination, size
211+
212+
def generate_checksum(self, binary):
213+
# S3 requires SHA256
214+
chsum = None
215+
if pecan.conf.storage_method == 's3':
216+
chsum = hashlib.sha256()
217+
else:
218+
chsum = hashlib.sha512()
219+
220+
with open(binary, 'rb') as f:
221+
for chunk in iter(lambda: f.read(4096), b''):
222+
chsum.update(chunk)
223+
return chsum.hexdigest()
180224

181225
@expose()
182226
def _lookup(self, name, *remainder):

chacra/models/binaries.py

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -168,27 +168,6 @@ def __json__(self):
168168

169169
# Listeners
170170

171-
172-
def generate_checksum(mapper, connection, target):
173-
try:
174-
target.path
175-
except AttributeError:
176-
target.checksum = None
177-
return
178-
179-
# FIXME
180-
# sometimes we can accept binaries without a path and that is probably something
181-
# that should not happen. The core purpose of this binary is that it works with
182-
# paths and files, this should be required.
183-
if not target.path:
184-
return
185-
chsum = hashlib.sha512()
186-
with open(target.path, 'rb') as f:
187-
for chunk in iter(lambda: f.read(4096), b''):
188-
chsum.update(chunk)
189-
target.checksum = chsum.hexdigest()
190-
191-
192171
def update_repo(mapper, connection, target):
193172
try:
194173
if target.repo.is_generic:
@@ -206,11 +185,6 @@ def update_repo(mapper, connection, target):
206185
# triggered it because there is nothing we need to do
207186
pass
208187

209-
# listen for checksum changes
210-
listen(Binary, 'before_insert', generate_checksum)
211-
listen(Binary, 'before_update', generate_checksum)
212-
213-
214188
def add_timestamp_listeners():
215189
# listen for timestamp modifications
216190
listen(Binary, 'before_insert', update_timestamp)

config/dev.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,11 +63,17 @@
6363
'encoding': 'utf-8'
6464
}
6565

66+
# Where to store the data. Options are 's3' or 'local'
67+
storage_method = 'local'
68+
6669
# location for storing uploaded binaries
6770
binary_root = '%(confdir)s/public'
6871
repos_root = '%(confdir)s/repos'
6972
distributions_root = '%(confdir)s/distributions'
7073

74+
# If storage method is s3, provide a bucket name
75+
bucket = ''
76+
7177
# When True it will set the headers so that Nginx can serve the download
7278
# instead of Pecan.
7379
delegate_downloads = False

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@ alembic
88
ipython
99
python-statsd
1010
requests
11+
boto3
1112
importlib_metadata<=3.6; python_version<'3.8'

0 commit comments

Comments
 (0)