aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndy Doan <andy.doan@linaro.org>2015-08-03 14:39:48 -0500
committerAndy Doan <andy.doan@linaro.org>2015-08-24 12:55:01 -0500
commitced5938521f7b595d25a2a0f201185acd58d8f60 (patch)
tree1f9c60c9e3629e09f05f6543c5024a4ae131b6b3
parent231df005bc3a4f62636bf24ea9954792043d0c84 (diff)
downloadlinaro-license-protection-ced5938521f7b595d25a2a0f201185acd58d8f60.tar.gz
s3: add support for latest link
S3 doesn't have a notion of symlinks. The easiest way to support this is to just copy things into latest and keep a ".s3_linked_from" file so we know its source. Change-Id: I7e8c606c62465e4074fe680f2b48e07a90a05b47
-rw-r--r--license_protected_downloads/api/v3.py48
-rw-r--r--license_protected_downloads/tests/test_api_v3.py64
-rw-r--r--urls.py2
3 files changed, 114 insertions, 0 deletions
diff --git a/license_protected_downloads/api/v3.py b/license_protected_downloads/api/v3.py
index 24a0bc3..a3c7c42 100644
--- a/license_protected_downloads/api/v3.py
+++ b/license_protected_downloads/api/v3.py
@@ -1,3 +1,5 @@
+import os
+
from django.conf import settings
from django.http import HttpResponse
from django.views.decorators.csrf import csrf_exempt
@@ -44,3 +46,49 @@ class PublishResource(v2.PublishResource):
@csrf_exempt
def publish(request, path):
return PublishResource(request, path).handle()
+
+
+class LatestLinkResource(PublishResource):
+ def POST(self):
+ b = S3Artifact.get_bucket()
+ if not b:
+ raise HttpResponseError('S3 is not enabled', 403)
+
+ if not self.path:
+ APILog.mark(self.request, 'INVALID_ARGUMENTS', self.api_key)
+ raise HttpResponseError('Invalid Arguments', 401)
+
+ path = self.path
+ if path[-1] == '/':
+ path = path[:-1] # make os.path.dirname give parent
+ path = settings.S3_PREFIX_PATH + path
+
+ items = list(b.list(delimiter='/', prefix=path + '/'))
+ if len(items) == 0:
+ APILog.mark(self.request, 'INVALID_ARGUMENTS', self.api_key)
+ raise HttpResponseError('Target does not exist: ' + self.path, 404)
+
+ link_name = self.request.POST.get('linkname', 'latest')
+ if link_name not in ('latest', 'lastSuccessful'):
+ APILog.mark(self.request, 'INVALID_ARGUMENTS', self.api_key)
+ raise HttpResponseError('Invalid link name', 401)
+
+ dst = os.path.join(os.path.dirname(path), link_name)
+ keys = b.list(dst)
+ b.delete_keys(keys)
+ for k in items:
+ newkeyname = k.name.replace(path, dst)
+ b.copy_key(newkeyname, k.bucket.name, k.name)
+ # keep track of where the link content came from
+ b.new_key(dst + '/.s3_linked_from').set_contents_from_string(path)
+
+ APILog.mark(self.request, 'LINK_LATEST', self.api_key)
+
+ resp = HttpResponse(status=201)
+ resp['Location'] = dst
+ return resp
+
+
+@csrf_exempt
+def link_latest(request, path):
+ return LatestLinkResource(request, path).handle()
diff --git a/license_protected_downloads/tests/test_api_v3.py b/license_protected_downloads/tests/test_api_v3.py
index 9c7ca9c..14ff50b 100644
--- a/license_protected_downloads/tests/test_api_v3.py
+++ b/license_protected_downloads/tests/test_api_v3.py
@@ -95,3 +95,67 @@ class APIv3PublishTests(TestCase):
k = self.bucket.get_key(settings.S3_PREFIX_PATH + 'a', validate=False)
k.set_contents_from_string(content)
self._post_file('/api/v3/publish/a', token, 'wont overwrite', 403)
+
+ def test_link_latest_simple(self):
+ content = 'build123'
+ token = APIToken.objects.create(key=self.api_key).token
+
+ self._post_file('/api/v3/publish/builds/123/a', token, content)
+ info = 'Format-Version: 0.5\n\nFiles-Pattern: *\nLicense-Type: open\n'
+ self._post_file(
+ '/api/v3/publish/builds/123/BUILD-INFO.txt', token, info)
+
+ resp = self.client.post(
+ '/api/v3/link_latest/builds/123', HTTP_AUTHTOKEN=token)
+ self.assertEqual(201, resp.status_code)
+
+ resp = self.client.get('/builds/latest/a')
+ self.assertEqual(302, resp.status_code)
+ resp = urllib2.urlopen(resp['Location'])
+ self.assertEqual(content, resp.read())
+
+ k = self.bucket.get_key(
+ settings.S3_PREFIX_PATH + 'builds/latest/.s3_linked_from')
+ self.assertEqual(
+ settings.S3_PREFIX_PATH + 'builds/123', k.get_contents_as_string())
+
+ def test_link_latest_trailing_slash(self):
+ content = 'build123'
+ token = APIToken.objects.create(key=self.api_key).token
+
+ self._post_file('/api/v3/publish/builds/123/a', token, content)
+ info = 'Format-Version: 0.5\n\nFiles-Pattern: *\nLicense-Type: open\n'
+ self._post_file(
+ '/api/v3/publish/builds/123/BUILD-INFO.txt', token, info)
+
+ resp = self.client.post(
+ '/api/v3/link_latest/builds/123/', HTTP_AUTHTOKEN=token)
+ self.assertEqual(201, resp.status_code)
+
+ resp = self.client.get('/builds/latest/a')
+ self.assertEqual(302, resp.status_code)
+ resp = urllib2.urlopen(resp['Location'])
+ self.assertEqual(content, resp.read())
+
+ def test_link_latest_bad(self):
+ token = APIToken.objects.create(key=self.api_key).token
+
+ resp = self.client.post(
+ '/api/v3/link_latest/builds/123', HTTP_AUTHTOKEN=token)
+ self.assertEqual(404, resp.status_code)
+
+ def test_link_latest_alt(self):
+ token = APIToken.objects.create(key=self.api_key).token
+ self._post_file('/api/v3/publish/buildX/a', token, 'content')
+
+ # ensure bad link name is caught
+ resp = self.client.post(
+ '/api/v3/link_latest/buildX', data={'linkname': 'foo'},
+ HTTP_AUTHTOKEN=token)
+ self.assertEqual(401, resp.status_code)
+
+ resp = self.client.post(
+ '/api/v3/link_latest/buildX',
+ data={'linkname': 'lastSuccessful'},
+ HTTP_AUTHTOKEN=token)
+ self.assertEqual(201, resp.status_code)
diff --git a/urls.py b/urls.py
index 14d818f..078f923 100644
--- a/urls.py
+++ b/urls.py
@@ -69,6 +69,8 @@ urlpatterns = patterns(
'license_protected_downloads.api.v3.token'),
url(r'^api/v3/publish/(?P<path>.*)$',
'license_protected_downloads.api.v3.publish'),
+ url(r'^api/v3/link_latest/(?P<path>.*)$',
+ 'license_protected_downloads.api.v3.link_latest'),
# Catch-all. We always return a file (or try to) if it exists.
# This handler does that.