diff --git a/data/userfiles.py b/data/userfiles.py index c63d8fe53..996e31cf0 100644 --- a/data/userfiles.py +++ b/data/userfiles.py @@ -4,6 +4,11 @@ import logging from boto.s3.key import Key from uuid import uuid4 +import hmac +import time +import urllib +import base64 +import sha logger = logging.getLogger(__name__) @@ -18,9 +23,25 @@ class UserRequestFiles(object): self._s3_conn = boto.s3.connection.S3Connection(s3_access_key, s3_secret_key, is_secure=False) + self._bucket_name = bucket_name self._bucket = self._s3_conn.get_bucket(bucket_name) + self._access_key = s3_access_key + self._secret_key = s3_secret_key self._prefix = 'userfiles' + def prepare_for_drop(self, mimeType): + """ Returns a signed URL to upload a file to our bucket. """ + file_id = str(self._prefix + '/' + str(uuid4())) + + expires = str(int(time.time() + 300)) + signingString = "PUT\n\n" + mimeType + "\n" + expires + "\n/" + self._bucket_name + "/" + file_id; + + hmac_signer = hmac.new(self._secret_key, signingString, sha) + signature = base64.b64encode(hmac_signer.digest()) + + url = "http://s3.amazonaws.com/" + self._bucket_name + "/" + file_id + "?AWSAccessKeyId=" + self._access_key + "&Expires=" + expires + "&Signature=" + urllib.quote(signature); + return (url, file_id) + def store_file(self, flask_file): file_id = str(uuid4()) full_key = os.path.join(self._prefix, file_id) diff --git a/endpoints/api.py b/endpoints/api.py index 5ba2272a0..2b233592f 100644 --- a/endpoints/api.py +++ b/endpoints/api.py @@ -394,14 +394,24 @@ def get_repo_builds(namespace, repository): abort(403) # Permissions denied + +@app.route('/api/filedrop/', methods=['POST']) +def get_filedrop_url(): + mimeType = request.get_json()['mimeType'] + (url, file_id) = user_files.prepare_for_drop(mimeType) + return jsonify({ + 'url': url, + 'file_id': file_id + }) + + @app.route('/api/repository//build/', methods=['POST']) @parse_repository_name def request_repo_build(namespace, repository): permission = ModifyRepositoryPermission(namespace, repository) if permission.can(): logger.debug('User requested repository initialization.') - dockerfile_source = request.files['initializedata'] - dockerfile_id = user_files.store_file(dockerfile_source) + dockerfile_id = request.get_json()['file_id'] repo = model.get_repository(namespace, repository) token = model.create_access_token(repo, 'write') @@ -412,7 +422,9 @@ def request_repo_build(namespace, repository): tag) dockerfile_build_queue.put(json.dumps({'request_id': build_request.id})) - return make_response('Created', 201) + return jsonify({ + 'started': True + }) abort(403) # Permissions denied diff --git a/static/js/controllers.js b/static/js/controllers.js index 984982c52..1e5493ec3 100644 --- a/static/js/controllers.js +++ b/static/js/controllers.js @@ -919,6 +919,71 @@ function NewRepoCtrl($scope, $location, UserService, Restangular) { 'initialize': false }; + var startBuild = function(repo, fileId) { + $scope.building = true; + + var data = { + 'file_id': fileId + }; + + var startBuildCall = Restangular.one('repository/' + repo.namespace + '/' + repo.name + '/build/'); + startBuildCall.customPOST(data).then(function(resp) { + $location.path('/repository/' + repo.namespace + '/' + repo.name); + }, function() { + $('#couldnotbuildModal').modal(); + $location.path('/repository/' + repo.namespace + '/' + repo.name); + }); + }; + + var conductUpload = function(repo, file, url, fileId, mimeType) { + var request = new XMLHttpRequest(); + request.open('PUT', url, true); + request.overrideMimeType(mimeType); + request.onprogress = function(e) { + var percentLoaded; + if (e.lengthComputable) { + $scope.upload_progress = (e.loaded / e.total) * 100; + } + }; + request.onerror = function() { + $('#couldnotbuildModal').modal(); + $location.path('/repository/' + repo.namespace + '/' + repo.name); + }; + request.onreadystatechange = function() { + var state = request.readyState; + if (state == 4) { + $scope.$apply(function() { + $scope.uploading = false; + startBuild(repo, fileId); + }); + return; + } + }; + request.send(file); + }; + + var startFileUpload = function(repo) { + $scope.uploading = true; + $scope.uploading_progress = 0; + + var uploader = $('#file-drop')[0]; + var file = uploader.files[0]; + $scope.upload_file = file.name; + + var mimeType = file.type || 'application/octet-stream'; + var data = { + 'mimeType': mimeType + }; + + var getUploadUrl = Restangular.one('filedrop/'); + getUploadUrl.customPOST(data).then(function(resp) { + conductUpload(repo, file, resp.url, resp.file_id, mimeType); + }, function() { + $('#couldnotbuildModal').modal(); + $location.path('/repository/' + repo.namespace + '/' + repo.name); + }); + }; + $scope.$watch( function () { return UserService.currentUser(); }, function (currentUser) { $scope.user = currentUser; @@ -950,6 +1015,12 @@ function NewRepoCtrl($scope, $location, UserService, Restangular) { }; $scope.createNewRepo = function() { + var uploader = $('#file-drop')[0]; + if ($scope.repo.initialize && uploader.files.length < 1) { + $('#missingfileModal').modal(); + return; + } + $scope.creating = true; var repo = $scope.repo; var data = { @@ -959,7 +1030,13 @@ function NewRepoCtrl($scope, $location, UserService, Restangular) { var createPost = Restangular.one('repository'); createPost.customPOST(data).then(function(created) { + $scope.creating = false; + // Repository created. Start the upload process if applicable. + if ($scope.repo.initialize) { + startFileUpload(created); + return; + } // Otherwise, redirect to the repo page. $location.path('/repository/' + created.namespace + '/' + created.name); diff --git a/static/partials/new-repo.html b/static/partials/new-repo.html index fb4ff2507..cde232546 100644 --- a/static/partials/new-repo.html +++ b/static/partials/new-repo.html @@ -1,8 +1,21 @@ +
+ +
+
-
+
+ Uploading file {{ upload_file }} +
+
+
+
+ +
+ +
@@ -69,7 +82,7 @@ Upload a Dockerfile or a zip file containing a Dockerfile in the root directory
- +
@@ -110,6 +123,26 @@ + + + + + + + + +