Get file dropping working and wire it up to call the build repo endpoint
This commit is contained in:
parent
fc6e3258a8
commit
28f6ff1605
4 changed files with 167 additions and 5 deletions
|
@ -4,6 +4,11 @@ import logging
|
||||||
|
|
||||||
from boto.s3.key import Key
|
from boto.s3.key import Key
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
import hmac
|
||||||
|
import time
|
||||||
|
import urllib
|
||||||
|
import base64
|
||||||
|
import sha
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
@ -18,9 +23,25 @@ class UserRequestFiles(object):
|
||||||
self._s3_conn = boto.s3.connection.S3Connection(s3_access_key,
|
self._s3_conn = boto.s3.connection.S3Connection(s3_access_key,
|
||||||
s3_secret_key,
|
s3_secret_key,
|
||||||
is_secure=False)
|
is_secure=False)
|
||||||
|
self._bucket_name = bucket_name
|
||||||
self._bucket = self._s3_conn.get_bucket(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'
|
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):
|
def store_file(self, flask_file):
|
||||||
file_id = str(uuid4())
|
file_id = str(uuid4())
|
||||||
full_key = os.path.join(self._prefix, file_id)
|
full_key = os.path.join(self._prefix, file_id)
|
||||||
|
|
|
@ -394,14 +394,24 @@ def get_repo_builds(namespace, repository):
|
||||||
abort(403) # Permissions denied
|
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/<path:repository>/build/', methods=['POST'])
|
@app.route('/api/repository/<path:repository>/build/', methods=['POST'])
|
||||||
@parse_repository_name
|
@parse_repository_name
|
||||||
def request_repo_build(namespace, repository):
|
def request_repo_build(namespace, repository):
|
||||||
permission = ModifyRepositoryPermission(namespace, repository)
|
permission = ModifyRepositoryPermission(namespace, repository)
|
||||||
if permission.can():
|
if permission.can():
|
||||||
logger.debug('User requested repository initialization.')
|
logger.debug('User requested repository initialization.')
|
||||||
dockerfile_source = request.files['initializedata']
|
dockerfile_id = request.get_json()['file_id']
|
||||||
dockerfile_id = user_files.store_file(dockerfile_source)
|
|
||||||
|
|
||||||
repo = model.get_repository(namespace, repository)
|
repo = model.get_repository(namespace, repository)
|
||||||
token = model.create_access_token(repo, 'write')
|
token = model.create_access_token(repo, 'write')
|
||||||
|
@ -412,7 +422,9 @@ def request_repo_build(namespace, repository):
|
||||||
tag)
|
tag)
|
||||||
dockerfile_build_queue.put(json.dumps({'request_id': build_request.id}))
|
dockerfile_build_queue.put(json.dumps({'request_id': build_request.id}))
|
||||||
|
|
||||||
return make_response('Created', 201)
|
return jsonify({
|
||||||
|
'started': True
|
||||||
|
})
|
||||||
|
|
||||||
abort(403) # Permissions denied
|
abort(403) # Permissions denied
|
||||||
|
|
||||||
|
|
|
@ -919,6 +919,71 @@ function NewRepoCtrl($scope, $location, UserService, Restangular) {
|
||||||
'initialize': false
|
'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.$watch( function () { return UserService.currentUser(); }, function (currentUser) {
|
||||||
$scope.user = currentUser;
|
$scope.user = currentUser;
|
||||||
|
|
||||||
|
@ -950,6 +1015,12 @@ function NewRepoCtrl($scope, $location, UserService, Restangular) {
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.createNewRepo = function() {
|
$scope.createNewRepo = function() {
|
||||||
|
var uploader = $('#file-drop')[0];
|
||||||
|
if ($scope.repo.initialize && uploader.files.length < 1) {
|
||||||
|
$('#missingfileModal').modal();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$scope.creating = true;
|
$scope.creating = true;
|
||||||
var repo = $scope.repo;
|
var repo = $scope.repo;
|
||||||
var data = {
|
var data = {
|
||||||
|
@ -959,7 +1030,13 @@ function NewRepoCtrl($scope, $location, UserService, Restangular) {
|
||||||
|
|
||||||
var createPost = Restangular.one('repository');
|
var createPost = Restangular.one('repository');
|
||||||
createPost.customPOST(data).then(function(created) {
|
createPost.customPOST(data).then(function(created) {
|
||||||
|
$scope.creating = false;
|
||||||
|
|
||||||
// Repository created. Start the upload process if applicable.
|
// Repository created. Start the upload process if applicable.
|
||||||
|
if ($scope.repo.initialize) {
|
||||||
|
startFileUpload(created);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Otherwise, redirect to the repo page.
|
// Otherwise, redirect to the repo page.
|
||||||
$location.path('/repository/' + created.namespace + '/' + created.name);
|
$location.path('/repository/' + created.namespace + '/' + created.name);
|
||||||
|
|
|
@ -1,8 +1,21 @@
|
||||||
|
<div class="container" ng-show="building">
|
||||||
|
<i class="fa fa-spinner fa-spin fa-3x"></i>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="container" ng-show="creating">
|
<div class="container" ng-show="creating">
|
||||||
<i class="fa fa-spinner fa-spin fa-3x"></i>
|
<i class="fa fa-spinner fa-spin fa-3x"></i>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="container new-repo" ng-show="!user.anonymous && !creating">
|
<div class="container" ng-show="uploading">
|
||||||
|
<span class="message">Uploading file {{ upload_file }}</span>
|
||||||
|
<div class="progress progress-striped active">
|
||||||
|
<div class="progress-bar" role="progressbar" aria-valuenow="{{ upload_progress }}" aria-valuemin="0" aria-valuemax="100" style="{{ 'width: ' + upload_progress + '%' }}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container new-repo" ng-show="!user.anonymous && !creating && !uploading && !building">
|
||||||
<form method="post" name="newRepoForm" ng-submit="createNewRepo()">
|
<form method="post" name="newRepoForm" ng-submit="createNewRepo()">
|
||||||
|
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
|
@ -69,7 +82,7 @@
|
||||||
Upload a Dockerfile or a zip file containing a Dockerfile <b>in the root directory</b>
|
Upload a Dockerfile or a zip file containing a Dockerfile <b>in the root directory</b>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<input class="file-drop" type="file">
|
<input id="file-drop" class="file-drop" type="file">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -110,6 +123,26 @@
|
||||||
</div><!-- /.modal -->
|
</div><!-- /.modal -->
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Modal message dialog -->
|
||||||
|
<div class="modal fade" id="missingfileModal">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||||
|
<h4 class="modal-title">File required</h4>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
A file is required in order to initialize a repository.
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
|
||||||
|
</div>
|
||||||
|
</div><!-- /.modal-content -->
|
||||||
|
</div><!-- /.modal-dialog -->
|
||||||
|
</div><!-- /.modal -->
|
||||||
|
|
||||||
|
|
||||||
<!-- Modal message dialog -->
|
<!-- Modal message dialog -->
|
||||||
<div class="modal fade" id="cannotcreateModal">
|
<div class="modal fade" id="cannotcreateModal">
|
||||||
<div class="modal-dialog">
|
<div class="modal-dialog">
|
||||||
|
@ -127,3 +160,22 @@
|
||||||
</div><!-- /.modal-content -->
|
</div><!-- /.modal-content -->
|
||||||
</div><!-- /.modal-dialog -->
|
</div><!-- /.modal-dialog -->
|
||||||
</div><!-- /.modal -->
|
</div><!-- /.modal -->
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Modal message dialog -->
|
||||||
|
<div class="modal fade" id="couldnotbuildModal">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||||
|
<h4 class="modal-title">Cannot initialize repository</h4>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
The repository could not be initialized with the selected Dockerfile. Please try again later.
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
|
||||||
|
</div>
|
||||||
|
</div><!-- /.modal-content -->
|
||||||
|
</div><!-- /.modal-dialog -->
|
||||||
|
</div><!-- /.modal -->
|
||||||
|
|
Reference in a new issue