Merge remote-tracking branch 'origin/master' into rustedbuilds
This commit is contained in:
commit
fc4983ed8b
75 changed files with 4280 additions and 700 deletions
|
@ -262,7 +262,6 @@ def convert_user_to_organization():
|
|||
@internal_api_call
|
||||
def change_user_details():
|
||||
user = current_user.db_user()
|
||||
|
||||
user_data = request.get_json()
|
||||
|
||||
try:
|
||||
|
@ -315,6 +314,8 @@ def create_new_user():
|
|||
@internal_api_call
|
||||
def signin_user():
|
||||
signin_data = request.get_json()
|
||||
if not signin_data:
|
||||
abort(404)
|
||||
|
||||
username = signin_data['username']
|
||||
password = signin_data['password']
|
||||
|
@ -421,6 +422,7 @@ def get_matching_entities(prefix):
|
|||
|
||||
team_data = [entity_team_view(team) for team in teams]
|
||||
user_data = [user_view(user) for user in users]
|
||||
|
||||
return jsonify({
|
||||
'results': team_data + user_data
|
||||
})
|
||||
|
@ -446,11 +448,16 @@ def create_organization():
|
|||
existing = None
|
||||
|
||||
try:
|
||||
existing = (model.get_organization(org_data['name']) or
|
||||
model.get_user(org_data['name']))
|
||||
existing = model.get_organization(org_data['name'])
|
||||
except model.InvalidOrganizationException:
|
||||
pass
|
||||
|
||||
if not existing:
|
||||
try:
|
||||
existing = model.get_user(org_data['name'])
|
||||
except model.InvalidUserException:
|
||||
pass
|
||||
|
||||
if existing:
|
||||
msg = 'A user or organization with this name already exists'
|
||||
return request_error(message=msg)
|
||||
|
@ -604,9 +611,9 @@ def create_organization_prototype_permission(orgname):
|
|||
'name' in details['activating_user']):
|
||||
activating_username = details['activating_user']['name']
|
||||
|
||||
delegate = details['delegate']
|
||||
delegate_kind = delegate['kind']
|
||||
delegate_name = delegate['name']
|
||||
delegate = details['delegate'] if 'delegate' in details else {}
|
||||
delegate_kind = delegate.get('kind', None)
|
||||
delegate_name = delegate.get('name', None)
|
||||
|
||||
delegate_username = delegate_name if delegate_kind == 'user' else None
|
||||
delegate_teamname = delegate_name if delegate_kind == 'team' else None
|
||||
|
@ -622,7 +629,7 @@ def create_organization_prototype_permission(orgname):
|
|||
return request_error(message='Unknown activating user')
|
||||
|
||||
if not delegate_user and not delegate_team:
|
||||
return request_error(message='Missing delagate user or team')
|
||||
return request_error(message='Missing delegate user or team')
|
||||
|
||||
role_name = details['role']
|
||||
|
||||
|
@ -1148,7 +1155,9 @@ def build_status_view(build_obj):
|
|||
'id': build_obj.uuid,
|
||||
'phase': build_obj.phase,
|
||||
'started': build_obj.started,
|
||||
'display_name': build_obj.display_name,
|
||||
'status': status,
|
||||
'resource_key': build_obj.resource_key
|
||||
}
|
||||
|
||||
|
||||
|
@ -1189,38 +1198,12 @@ def get_repo_build_logs(namespace, repository, build_uuid):
|
|||
|
||||
build = model.get_repository_build(namespace, repository, build_uuid)
|
||||
|
||||
start_param = request.args.get('start', None)
|
||||
end = int(request.args.get('end', -1))
|
||||
start = int(request.args.get('start', 0))
|
||||
|
||||
last_command = None
|
||||
include_commands = request.args.get('commands', 'false')
|
||||
if include_commands.lower() not in {'0', 'false'}:
|
||||
commands = [cmd for cmd in build_logs.get_commands(build.uuid)]
|
||||
response_obj['commands'] = commands
|
||||
if commands:
|
||||
last_command = commands[-1]
|
||||
elif start_param is None:
|
||||
last_command = build_logs.get_last_command(build.uuid)
|
||||
|
||||
if start_param is None:
|
||||
if last_command:
|
||||
start = last_command['index']
|
||||
else:
|
||||
start = 0
|
||||
else:
|
||||
start = int(start_param)
|
||||
|
||||
count, logs = build_logs.get_log_entries(build.uuid, start, end)
|
||||
|
||||
if start < 0:
|
||||
start = max(0, count + start)
|
||||
|
||||
if end < 0:
|
||||
end = count + end
|
||||
count, logs = build_logs.get_log_entries(build.uuid, start)
|
||||
|
||||
response_obj.update({
|
||||
'start': start,
|
||||
'end': end,
|
||||
'total': count,
|
||||
'logs': [log for log in logs],
|
||||
})
|
||||
|
@ -1239,18 +1222,28 @@ def request_repo_build(namespace, repository):
|
|||
logger.debug('User requested repository initialization.')
|
||||
dockerfile_id = request.get_json()['file_id']
|
||||
|
||||
# Check if the dockerfile resource has already been used. If so, then it can only be reused if the
|
||||
# user has access to the repository for which it was used.
|
||||
associated_repository = model.get_repository_for_resource(dockerfile_id)
|
||||
if associated_repository:
|
||||
if not ModifyRepositoryPermission(associated_repository.namespace, associated_repository.name):
|
||||
abort(403)
|
||||
|
||||
# Start the build.
|
||||
repo = model.get_repository(namespace, repository)
|
||||
token = model.create_access_token(repo, 'write')
|
||||
display_name = user_files.get_file_checksum(dockerfile_id)
|
||||
logger.debug('**********Md5: %s' % display_name)
|
||||
|
||||
host = urlparse.urlparse(request.url).netloc
|
||||
tag = '%s/%s/%s' % (host, repo.namespace, repo.name)
|
||||
build_request = model.create_repository_build(repo, token, dockerfile_id,
|
||||
tag)
|
||||
tag, display_name)
|
||||
dockerfile_build_queue.put(json.dumps({
|
||||
'build_uuid': build_request.uuid,
|
||||
'namespace': namespace,
|
||||
'repository': repository,
|
||||
}))
|
||||
}), retries_remaining=1)
|
||||
|
||||
log_action('build_dockerfile', namespace,
|
||||
{'repo': repository, 'namespace': namespace,
|
||||
|
@ -1302,7 +1295,11 @@ def create_webhook(namespace, repository):
|
|||
def get_webhook(namespace, repository, public_id):
|
||||
permission = AdministerRepositoryPermission(namespace, repository)
|
||||
if permission.can():
|
||||
webhook = model.get_webhook(namespace, repository, public_id)
|
||||
try:
|
||||
webhook = model.get_webhook(namespace, repository, public_id)
|
||||
except model.InvalidWebhookException:
|
||||
abort(404)
|
||||
|
||||
return jsonify(webhook_view(webhook))
|
||||
|
||||
abort(403) # Permission denied
|
||||
|
@ -1697,7 +1694,11 @@ def list_repo_tokens(namespace, repository):
|
|||
def get_tokens(namespace, repository, code):
|
||||
permission = AdministerRepositoryPermission(namespace, repository)
|
||||
if permission.can():
|
||||
perm = model.get_repo_delegate_token(namespace, repository, code)
|
||||
try:
|
||||
perm = model.get_repo_delegate_token(namespace, repository, code)
|
||||
except model.InvalidTokenException:
|
||||
abort(404)
|
||||
|
||||
return jsonify(token_view(perm))
|
||||
|
||||
abort(403) # Permission denied
|
||||
|
@ -1834,6 +1835,8 @@ def set_card(user, token):
|
|||
cus.save()
|
||||
except stripe.CardError as e:
|
||||
return carderror_response(e)
|
||||
except stripe.InvalidRequestError as e:
|
||||
return carderror_response(e)
|
||||
|
||||
return get_card(user)
|
||||
|
||||
|
|
|
@ -5,9 +5,9 @@ import urlparse
|
|||
from flask import request, make_response, jsonify, session, Blueprint
|
||||
from functools import wraps
|
||||
|
||||
from data import model
|
||||
from data import model, userevent
|
||||
from data.queue import webhook_queue
|
||||
from app import mixpanel
|
||||
from app import mixpanel, app
|
||||
from auth.auth import (process_auth, get_authenticated_user,
|
||||
get_validated_token)
|
||||
from util.names import parse_repository_name
|
||||
|
@ -80,8 +80,16 @@ def create_user():
|
|||
if existing_user:
|
||||
verified = model.verify_user(username, password)
|
||||
if verified:
|
||||
# Mark that the user was logged in.
|
||||
event = app.config['USER_EVENTS'].get_event(username)
|
||||
event.publish_event_data('docker-cli', {'action': 'login'})
|
||||
|
||||
return make_response('Verified', 201)
|
||||
else:
|
||||
# Mark that the login failed.
|
||||
event = app.config['USER_EVENTS'].get_event(username)
|
||||
event.publish_event_data('docker-cli', {'action': 'loginfailure'})
|
||||
|
||||
abort(400, 'Invalid password.', issue='login-failure')
|
||||
|
||||
else:
|
||||
|
@ -186,9 +194,21 @@ def create_repository(namespace, repository):
|
|||
}
|
||||
|
||||
if get_authenticated_user():
|
||||
mixpanel.track(get_authenticated_user().username, 'push_repo',
|
||||
extra_params)
|
||||
metadata['username'] = get_authenticated_user().username
|
||||
username = get_authenticated_user().username
|
||||
|
||||
mixpanel.track(username, 'push_repo', extra_params)
|
||||
metadata['username'] = username
|
||||
|
||||
# Mark that the user has started pushing the repo.
|
||||
user_data = {
|
||||
'action': 'push_repo',
|
||||
'repository': repository,
|
||||
'namespace': namespace
|
||||
}
|
||||
|
||||
event = app.config['USER_EVENTS'].get_event(username)
|
||||
event.publish_event_data('docker-cli', user_data)
|
||||
|
||||
else:
|
||||
mixpanel.track(get_validated_token().code, 'push_repo', extra_params)
|
||||
metadata['token'] = get_validated_token().friendly_name
|
||||
|
@ -222,6 +242,21 @@ def update_images(namespace, repository):
|
|||
updated_tags[image['Tag']] = image['id']
|
||||
model.set_image_checksum(image['id'], repo, image['checksum'])
|
||||
|
||||
if get_authenticated_user():
|
||||
username = get_authenticated_user().username
|
||||
|
||||
# Mark that the user has pushed the repo.
|
||||
user_data = {
|
||||
'action': 'pushed_repo',
|
||||
'repository': repository,
|
||||
'namespace': namespace
|
||||
}
|
||||
|
||||
event = app.config['USER_EVENTS'].get_event(username)
|
||||
event.publish_event_data('docker-cli', user_data)
|
||||
|
||||
num_removed = model.garbage_collect_repository(namespace, repository)
|
||||
|
||||
# Generate a job for each webhook that has been added to this repo
|
||||
webhooks = model.list_webhooks(namespace, repository)
|
||||
for webhook in webhooks:
|
||||
|
@ -237,7 +272,8 @@ def update_images(namespace, repository):
|
|||
'homepage': 'https://quay.io/repository/%s' % repo_string,
|
||||
'visibility': repo.visibility.name,
|
||||
'updated_tags': updated_tags,
|
||||
'pushed_image_count': len(image_with_checksums),
|
||||
'pushed_image_count': len(image_with_checksums),
|
||||
'pruned_image_count': num_removed,
|
||||
}
|
||||
webhook_queue.put(json.dumps(webhook_data))
|
||||
|
||||
|
|
81
endpoints/realtime.py
Normal file
81
endpoints/realtime.py
Normal file
|
@ -0,0 +1,81 @@
|
|||
import logging
|
||||
import redis
|
||||
import json
|
||||
|
||||
from functools import wraps
|
||||
from flask import request, make_response, Blueprint, abort, Response
|
||||
from flask.ext.login import current_user, logout_user
|
||||
from data import model, userevent
|
||||
from app import app
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
realtime = Blueprint('realtime', __name__)
|
||||
|
||||
def api_login_required(f):
|
||||
@wraps(f)
|
||||
def decorated_view(*args, **kwargs):
|
||||
if not current_user.is_authenticated():
|
||||
abort(401)
|
||||
|
||||
if (current_user and current_user.db_user() and
|
||||
current_user.db_user().organization):
|
||||
abort(401)
|
||||
|
||||
if (current_user and current_user.db_user() and
|
||||
current_user.db_user().robot):
|
||||
abort(401)
|
||||
|
||||
return f(*args, **kwargs)
|
||||
return decorated_view
|
||||
|
||||
|
||||
@realtime.route("/user/")
|
||||
@api_login_required
|
||||
def index():
|
||||
debug_template = """
|
||||
<html>
|
||||
<head>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Server sent events</h1>
|
||||
<div id="event"></div>
|
||||
<script type="text/javascript">
|
||||
|
||||
var eventOutputContainer = document.getElementById("event");
|
||||
var evtSrc = new EventSource("/realtime/user/subscribe?events=docker-cli");
|
||||
|
||||
evtSrc.onmessage = function(e) {
|
||||
console.log(e.data);
|
||||
eventOutputContainer.innerHTML = e.data;
|
||||
};
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
return(debug_template)
|
||||
|
||||
|
||||
@realtime.route("/user/test")
|
||||
@api_login_required
|
||||
def user_test():
|
||||
evt = userevent.UserEvent('logs.quay.io', current_user.db_user().username)
|
||||
evt.publish_event_data('test', {'foo': 2})
|
||||
return 'OK'
|
||||
|
||||
@realtime.route("/user/subscribe")
|
||||
@api_login_required
|
||||
def user_subscribe():
|
||||
def wrapper(listener):
|
||||
for event_id, data in listener.event_stream():
|
||||
message = {'event': event_id, 'data': data}
|
||||
json_string = json.dumps(message)
|
||||
yield 'data: %s\n\n' % json_string
|
||||
|
||||
events = request.args.get('events', '').split(',')
|
||||
if not events:
|
||||
abort(404)
|
||||
|
||||
listener = userevent.UserEventListener('logs.quay.io', current_user.db_user().username, events)
|
||||
return Response(wrapper(listener), mimetype="text/event-stream")
|
|
@ -73,22 +73,8 @@ def delete_tag(namespace, repository, tag):
|
|||
|
||||
if permission.can():
|
||||
model.delete_tag(namespace, repository, tag)
|
||||
model.garbage_collect_repository(namespace, repository)
|
||||
|
||||
return make_response('Deleted', 204)
|
||||
|
||||
abort(403)
|
||||
|
||||
|
||||
@tags.route('/repositories/<path:repository>/tags',
|
||||
methods=['DELETE'])
|
||||
@process_auth
|
||||
@parse_repository_name
|
||||
def delete_repository_tags(namespace, repository):
|
||||
permission = ModifyRepositoryPermission(namespace, repository)
|
||||
|
||||
if permission.can():
|
||||
model.delete_all_repository_tags(namespace, repository)
|
||||
|
||||
return make_response('Deleted', 204)
|
||||
return make_response('Deleted', 200)
|
||||
|
||||
abort(403)
|
||||
|
|
|
@ -63,6 +63,12 @@ def guide():
|
|||
return index('')
|
||||
|
||||
|
||||
@web.route('/tutorial/')
|
||||
@no_cache
|
||||
def tutorial():
|
||||
return index('')
|
||||
|
||||
|
||||
@web.route('/organizations/')
|
||||
@web.route('/organizations/new/')
|
||||
@no_cache
|
||||
|
@ -88,6 +94,12 @@ def contact():
|
|||
return index('')
|
||||
|
||||
|
||||
@web.route('/about/')
|
||||
@no_cache
|
||||
def about():
|
||||
return index('')
|
||||
|
||||
|
||||
@web.route('/new/')
|
||||
@no_cache
|
||||
def new():
|
||||
|
|
Reference in a new issue