2013-09-20 15:55:44 +00:00
|
|
|
import json
|
|
|
|
import logging
|
2013-10-29 20:11:54 +00:00
|
|
|
import urlparse
|
2013-09-20 15:55:44 +00:00
|
|
|
|
|
|
|
from functools import wraps
|
|
|
|
|
2016-03-09 21:20:28 +00:00
|
|
|
from flask import request, make_response, jsonify, session
|
|
|
|
|
2016-09-23 21:50:09 +00:00
|
|
|
from data.interfaces.v1 import pre_oci_model as model
|
2016-09-09 19:13:58 +00:00
|
|
|
from app import authentication, userevents, metric_queue
|
2014-03-26 20:19:04 +00:00
|
|
|
from auth.auth_context import get_authenticated_user, get_validated_token, get_validated_oauth_token
|
2014-03-18 23:21:27 +00:00
|
|
|
from auth.permissions import (ModifyRepositoryPermission, UserAdminPermission,
|
2015-02-19 21:54:23 +00:00
|
|
|
ReadRepositoryPermission, CreateRepositoryPermission,
|
2015-07-15 21:25:41 +00:00
|
|
|
repository_read_grant, repository_write_grant)
|
2016-09-29 19:24:57 +00:00
|
|
|
from auth.process import process_auth, generate_signed_token
|
2014-01-24 20:01:40 +00:00
|
|
|
from util.http import abort
|
2016-03-09 21:20:28 +00:00
|
|
|
from util.names import REPOSITORY_NAME_REGEX
|
2016-01-21 20:40:51 +00:00
|
|
|
from endpoints.common import parse_repository_name
|
2015-06-22 21:37:13 +00:00
|
|
|
from endpoints.v1 import v1_bp
|
2014-10-30 16:49:51 +00:00
|
|
|
from endpoints.trackhelper import track_and_log
|
2014-07-18 20:34:52 +00:00
|
|
|
from endpoints.notificationhelper import spawn_notification
|
2015-06-02 19:56:44 +00:00
|
|
|
from endpoints.decorators import anon_protect, anon_allowed
|
2014-01-24 20:01:40 +00:00
|
|
|
|
2013-09-20 15:55:44 +00:00
|
|
|
|
2015-06-22 21:37:13 +00:00
|
|
|
logger = logging.getLogger(__name__)
|
2013-09-20 15:55:44 +00:00
|
|
|
|
2015-02-19 21:54:23 +00:00
|
|
|
|
|
|
|
class GrantType(object):
|
|
|
|
READ_REPOSITORY = 'read'
|
|
|
|
WRITE_REPOSITORY = 'write'
|
|
|
|
|
|
|
|
|
2015-06-10 19:16:01 +00:00
|
|
|
def generate_headers(scope=GrantType.READ_REPOSITORY, add_grant_for_status=None):
|
2013-10-16 18:24:10 +00:00
|
|
|
def decorator_method(f):
|
|
|
|
@wraps(f)
|
2016-03-09 21:20:28 +00:00
|
|
|
def wrapper(namespace_name, repo_name, *args, **kwargs):
|
|
|
|
response = f(namespace_name, repo_name, *args, **kwargs)
|
2013-09-20 15:55:44 +00:00
|
|
|
|
2013-12-09 04:24:29 +00:00
|
|
|
# Setting session namespace and repository
|
2016-03-09 21:20:28 +00:00
|
|
|
session['namespace'] = namespace_name
|
|
|
|
session['repository'] = repo_name
|
2015-06-02 19:16:22 +00:00
|
|
|
|
2013-10-29 20:11:54 +00:00
|
|
|
# We run our index and registry on the same hosts for now
|
|
|
|
registry_server = urlparse.urlparse(request.url).netloc
|
|
|
|
response.headers['X-Docker-Endpoints'] = registry_server
|
2013-09-20 15:55:44 +00:00
|
|
|
|
2013-10-16 18:24:10 +00:00
|
|
|
has_token_request = request.headers.get('X-Docker-Token', '')
|
2015-06-10 19:16:01 +00:00
|
|
|
force_grant = (add_grant_for_status == response.status_code)
|
2013-09-20 22:38:17 +00:00
|
|
|
|
2015-06-10 19:16:01 +00:00
|
|
|
if has_token_request or force_grant:
|
2015-02-19 21:54:23 +00:00
|
|
|
grants = []
|
2015-06-02 19:16:22 +00:00
|
|
|
|
2015-02-19 21:54:23 +00:00
|
|
|
if scope == GrantType.READ_REPOSITORY:
|
2016-03-09 21:20:28 +00:00
|
|
|
if force_grant or ReadRepositoryPermission(namespace_name, repo_name).can():
|
|
|
|
grants.append(repository_read_grant(namespace_name, repo_name))
|
2015-02-19 21:54:23 +00:00
|
|
|
elif scope == GrantType.WRITE_REPOSITORY:
|
2016-03-09 21:20:28 +00:00
|
|
|
if force_grant or ModifyRepositoryPermission(namespace_name, repo_name).can():
|
|
|
|
grants.append(repository_write_grant(namespace_name, repo_name))
|
2015-02-19 21:54:23 +00:00
|
|
|
|
2015-06-02 19:16:22 +00:00
|
|
|
# Generate a signed token for the user (if any) and the grants (if any)
|
|
|
|
if grants or get_authenticated_user():
|
2015-02-24 18:22:19 +00:00
|
|
|
user_context = get_authenticated_user() and get_authenticated_user().username
|
|
|
|
signature = generate_signed_token(grants, user_context)
|
|
|
|
response.headers['WWW-Authenticate'] = signature
|
|
|
|
response.headers['X-Docker-Token'] = signature
|
2015-06-02 19:16:22 +00:00
|
|
|
|
2013-10-16 18:24:10 +00:00
|
|
|
return response
|
|
|
|
return wrapper
|
|
|
|
return decorator_method
|
2013-09-20 15:55:44 +00:00
|
|
|
|
|
|
|
|
2015-06-22 21:37:13 +00:00
|
|
|
@v1_bp.route('/users', methods=['POST'])
|
|
|
|
@v1_bp.route('/users/', methods=['POST'])
|
2015-06-02 19:56:44 +00:00
|
|
|
@anon_allowed
|
2013-09-20 15:55:44 +00:00
|
|
|
def create_user():
|
|
|
|
user_data = request.get_json()
|
2014-10-14 19:46:35 +00:00
|
|
|
if not user_data or not 'username' in user_data:
|
2014-09-15 15:27:33 +00:00
|
|
|
abort(400, 'Missing username')
|
|
|
|
|
2013-10-01 16:25:06 +00:00
|
|
|
username = user_data['username']
|
2014-07-15 01:24:38 +00:00
|
|
|
password = user_data.get('password', '')
|
2013-10-01 16:25:06 +00:00
|
|
|
|
2014-02-20 19:49:34 +00:00
|
|
|
# UGH! we have to use this response when the login actually worked, in order
|
|
|
|
# to get the CLI to try again with a get, and then tell us login succeeded.
|
|
|
|
success = make_response('"Username or email already exists"', 400)
|
|
|
|
|
2013-10-16 18:24:10 +00:00
|
|
|
if username == '$token':
|
2016-08-30 19:05:15 +00:00
|
|
|
if model.load_token(password):
|
2014-02-20 19:49:34 +00:00
|
|
|
return success
|
2016-08-19 18:00:21 +00:00
|
|
|
abort(400, 'Invalid access token.', issue='invalid-access-token')
|
2013-10-16 18:24:10 +00:00
|
|
|
|
2014-03-20 16:09:25 +00:00
|
|
|
elif username == '$oauthtoken':
|
2016-08-30 19:05:15 +00:00
|
|
|
if model.validate_oauth_token(password):
|
2014-03-20 16:09:25 +00:00
|
|
|
return success
|
2016-08-19 18:00:21 +00:00
|
|
|
abort(400, 'Invalid oauth access token.', issue='invalid-oauth-access-token')
|
2014-03-20 16:09:25 +00:00
|
|
|
|
2013-11-21 00:43:19 +00:00
|
|
|
elif '+' in username:
|
2016-08-30 19:05:15 +00:00
|
|
|
if model.verify_robot(username, password):
|
2014-02-20 19:49:34 +00:00
|
|
|
return success
|
2016-08-19 18:00:21 +00:00
|
|
|
abort(400, 'Invalid robot account or password.', issue='robot-login-failure')
|
2013-11-21 00:43:19 +00:00
|
|
|
|
2015-07-20 15:39:59 +00:00
|
|
|
(verified, error_message) = authentication.verify_and_link_user(username, password,
|
|
|
|
basic_auth=True)
|
2015-06-22 21:12:05 +00:00
|
|
|
if verified:
|
|
|
|
# Mark that the user was logged in.
|
|
|
|
event = userevents.get_event(username)
|
|
|
|
event.publish_event_data('docker-cli', {'action': 'login'})
|
|
|
|
return success
|
2013-10-01 16:25:06 +00:00
|
|
|
else:
|
2015-06-22 21:12:05 +00:00
|
|
|
# Mark that the login failed.
|
|
|
|
event = userevents.get_event(username)
|
|
|
|
event.publish_event_data('docker-cli', {'action': 'loginfailure'})
|
|
|
|
abort(400, error_message, issue='login-failure')
|
2013-09-20 15:55:44 +00:00
|
|
|
|
|
|
|
|
2015-06-22 21:37:13 +00:00
|
|
|
@v1_bp.route('/users', methods=['GET'])
|
|
|
|
@v1_bp.route('/users/', methods=['GET'])
|
2013-09-20 22:38:17 +00:00
|
|
|
@process_auth
|
2015-06-02 19:56:44 +00:00
|
|
|
@anon_allowed
|
2013-09-20 15:55:44 +00:00
|
|
|
def get_user():
|
2014-03-26 20:19:04 +00:00
|
|
|
if get_validated_oauth_token():
|
|
|
|
return jsonify({
|
|
|
|
'username': '$oauthtoken',
|
|
|
|
'email': None,
|
|
|
|
})
|
2014-11-24 21:07:38 +00:00
|
|
|
elif get_authenticated_user():
|
2013-11-07 22:09:47 +00:00
|
|
|
return jsonify({
|
|
|
|
'username': get_authenticated_user().username,
|
|
|
|
'email': get_authenticated_user().email,
|
|
|
|
})
|
2014-02-21 21:07:08 +00:00
|
|
|
elif get_validated_token():
|
|
|
|
return jsonify({
|
|
|
|
'username': '$token',
|
|
|
|
'email': None,
|
|
|
|
})
|
2013-11-07 22:09:47 +00:00
|
|
|
abort(404)
|
2013-09-20 15:55:44 +00:00
|
|
|
|
|
|
|
|
2015-06-22 21:37:13 +00:00
|
|
|
@v1_bp.route('/users/<username>/', methods=['PUT'])
|
2013-09-20 22:38:17 +00:00
|
|
|
@process_auth
|
2015-06-02 19:56:44 +00:00
|
|
|
@anon_allowed
|
2013-09-20 15:55:44 +00:00
|
|
|
def update_user(username):
|
2014-03-18 23:21:27 +00:00
|
|
|
permission = UserAdminPermission(username)
|
2013-09-20 22:38:17 +00:00
|
|
|
if permission.can():
|
|
|
|
update_request = request.get_json()
|
2013-09-20 15:55:44 +00:00
|
|
|
|
2013-09-20 22:38:17 +00:00
|
|
|
if 'password' in update_request:
|
2015-02-11 19:15:18 +00:00
|
|
|
logger.debug('Updating user password')
|
2016-08-30 19:05:15 +00:00
|
|
|
model.change_user_password(get_authenticated_user(), update_request['password'])
|
2013-09-20 15:55:44 +00:00
|
|
|
|
2013-09-20 22:38:17 +00:00
|
|
|
return jsonify({
|
|
|
|
'username': get_authenticated_user().username,
|
2016-08-19 18:00:21 +00:00
|
|
|
'email': get_authenticated_user().email
|
2013-09-20 22:38:17 +00:00
|
|
|
})
|
|
|
|
abort(403)
|
2013-09-20 15:55:44 +00:00
|
|
|
|
|
|
|
|
2016-01-29 14:42:15 +00:00
|
|
|
@v1_bp.route('/repositories/<repopath:repository>/', methods=['PUT'])
|
2013-09-20 22:38:17 +00:00
|
|
|
@process_auth
|
2016-03-09 21:20:28 +00:00
|
|
|
@parse_repository_name()
|
2015-06-10 19:16:01 +00:00
|
|
|
@generate_headers(scope=GrantType.WRITE_REPOSITORY, add_grant_for_status=201)
|
2015-06-02 19:56:44 +00:00
|
|
|
@anon_allowed
|
2016-03-09 21:20:28 +00:00
|
|
|
def create_repository(namespace_name, repo_name):
|
2015-09-24 15:42:56 +00:00
|
|
|
# Verify that the repository name is valid.
|
2016-03-09 21:20:28 +00:00
|
|
|
if not REPOSITORY_NAME_REGEX.match(repo_name):
|
2015-09-24 15:42:56 +00:00
|
|
|
abort(400, message='Invalid repository name. Repository names cannot contain slashes.')
|
|
|
|
|
2016-03-09 21:20:28 +00:00
|
|
|
logger.debug('Looking up repository %s/%s', namespace_name, repo_name)
|
2016-08-30 19:05:15 +00:00
|
|
|
repo = model.get_repository(namespace_name, repo_name)
|
2013-09-20 22:38:17 +00:00
|
|
|
|
2016-03-09 21:20:28 +00:00
|
|
|
logger.debug('Found repository %s/%s', namespace_name, repo_name)
|
2013-10-16 18:24:10 +00:00
|
|
|
if not repo and get_authenticated_user() is None:
|
2016-03-09 21:20:28 +00:00
|
|
|
logger.debug('Attempt to create repository %s/%s without user auth', namespace_name, repo_name)
|
2014-01-28 23:29:45 +00:00
|
|
|
abort(401,
|
|
|
|
message='Cannot create a repository as a guest. Please login via "docker login" first.',
|
|
|
|
issue='no-login')
|
2013-10-16 18:24:10 +00:00
|
|
|
|
|
|
|
elif repo:
|
2016-08-19 18:00:21 +00:00
|
|
|
modify_perm = ModifyRepositoryPermission(namespace_name, repo_name)
|
|
|
|
if not modify_perm.can():
|
2014-11-24 21:07:38 +00:00
|
|
|
abort(403,
|
2014-01-28 23:29:45 +00:00
|
|
|
message='You do not have permission to modify repository %(namespace)s/%(repository)s',
|
|
|
|
issue='no-repo-write-permission',
|
2016-03-09 21:20:28 +00:00
|
|
|
namespace=namespace_name, repository=repo_name)
|
2013-09-26 19:58:11 +00:00
|
|
|
else:
|
2016-08-19 18:00:21 +00:00
|
|
|
create_perm = CreateRepositoryPermission(namespace_name)
|
|
|
|
if not create_perm.can():
|
2016-03-09 21:20:28 +00:00
|
|
|
logger.info('Attempt to create a new repo %s/%s with insufficient perms', namespace_name,
|
|
|
|
repo_name)
|
2015-07-15 21:25:41 +00:00
|
|
|
msg = 'You do not have permission to create repositories in namespace "%(namespace)s"'
|
2016-03-09 21:20:28 +00:00
|
|
|
abort(403, message=msg, issue='no-create-permission', namespace=namespace_name)
|
2013-09-20 22:38:17 +00:00
|
|
|
|
2015-06-10 19:16:01 +00:00
|
|
|
# Attempt to create the new repository.
|
2016-03-09 21:20:28 +00:00
|
|
|
logger.debug('Creating repository %s/%s with owner: %s', namespace_name, repo_name,
|
2015-06-10 19:16:01 +00:00
|
|
|
get_authenticated_user().username)
|
|
|
|
|
2016-08-30 19:05:15 +00:00
|
|
|
model.create_repository(namespace_name, repo_name, get_authenticated_user())
|
2013-09-20 15:55:44 +00:00
|
|
|
|
2015-05-13 18:55:39 +00:00
|
|
|
if get_authenticated_user():
|
|
|
|
user_event_data = {
|
|
|
|
'action': 'push_start',
|
2016-03-09 21:20:28 +00:00
|
|
|
'repository': repo_name,
|
|
|
|
'namespace': namespace_name,
|
2015-05-13 18:55:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
event = userevents.get_event(get_authenticated_user().username)
|
|
|
|
event.publish_event_data('docker-cli', user_event_data)
|
2015-04-03 16:13:33 +00:00
|
|
|
|
2014-11-06 19:48:16 +00:00
|
|
|
return make_response('Created', 201)
|
2013-09-20 15:55:44 +00:00
|
|
|
|
|
|
|
|
2016-01-21 20:40:51 +00:00
|
|
|
@v1_bp.route('/repositories/<repopath:repository>/images', methods=['PUT'])
|
2013-09-20 22:38:17 +00:00
|
|
|
@process_auth
|
2016-03-09 21:20:28 +00:00
|
|
|
@parse_repository_name()
|
2015-02-19 21:54:23 +00:00
|
|
|
@generate_headers(scope=GrantType.WRITE_REPOSITORY)
|
2015-06-02 19:56:44 +00:00
|
|
|
@anon_allowed
|
2016-03-09 21:20:28 +00:00
|
|
|
def update_images(namespace_name, repo_name):
|
|
|
|
permission = ModifyRepositoryPermission(namespace_name, repo_name)
|
2013-09-20 22:38:17 +00:00
|
|
|
|
|
|
|
if permission.can():
|
2015-02-11 19:15:18 +00:00
|
|
|
logger.debug('Looking up repository')
|
2016-08-30 19:05:15 +00:00
|
|
|
repo = model.get_repository(namespace_name, repo_name)
|
2013-11-16 20:05:26 +00:00
|
|
|
if not repo:
|
2013-11-11 23:05:21 +00:00
|
|
|
# Make sure the repo actually exists.
|
2014-01-28 23:29:45 +00:00
|
|
|
abort(404, message='Unknown repository', issue='unknown-repo')
|
2013-11-11 23:05:21 +00:00
|
|
|
|
2014-07-18 02:51:58 +00:00
|
|
|
# Generate a job for each notification that has been added to this repo
|
2015-02-11 19:15:18 +00:00
|
|
|
logger.debug('Adding notifications for repository')
|
2014-07-18 02:51:58 +00:00
|
|
|
|
2014-10-22 18:14:56 +00:00
|
|
|
updated_tags = session.get('pushed_tags', {})
|
2014-07-18 02:51:58 +00:00
|
|
|
event_data = {
|
|
|
|
'updated_tags': updated_tags,
|
|
|
|
}
|
2015-04-03 16:13:33 +00:00
|
|
|
|
2015-02-13 21:28:45 +00:00
|
|
|
track_and_log('push_repo', repo)
|
2014-07-18 19:58:18 +00:00
|
|
|
spawn_notification(repo, 'repo_push', event_data)
|
2016-11-03 19:28:40 +00:00
|
|
|
metric_queue.repository_push.Inc(labelvalues=[namespace_name, repo_name, 'v1', True])
|
2013-09-20 22:38:17 +00:00
|
|
|
return make_response('Updated', 204)
|
|
|
|
|
|
|
|
abort(403)
|
2013-09-20 15:55:44 +00:00
|
|
|
|
|
|
|
|
2016-01-21 20:40:51 +00:00
|
|
|
@v1_bp.route('/repositories/<repopath:repository>/images', methods=['GET'])
|
2013-09-20 22:38:17 +00:00
|
|
|
@process_auth
|
2016-03-09 21:20:28 +00:00
|
|
|
@parse_repository_name()
|
2015-02-19 21:54:23 +00:00
|
|
|
@generate_headers(scope=GrantType.READ_REPOSITORY)
|
2015-05-19 21:52:44 +00:00
|
|
|
@anon_protect
|
2016-03-09 21:20:28 +00:00
|
|
|
def get_repository_images(namespace_name, repo_name):
|
|
|
|
permission = ReadRepositoryPermission(namespace_name, repo_name)
|
2013-09-20 15:55:44 +00:00
|
|
|
|
2013-09-20 22:38:17 +00:00
|
|
|
# TODO invalidate token?
|
2016-08-30 19:05:15 +00:00
|
|
|
if permission.can() or model.repository_is_public(namespace_name, repo_name):
|
2013-11-11 23:05:21 +00:00
|
|
|
# We can't rely on permissions to tell us if a repo exists anymore
|
2015-02-11 19:15:18 +00:00
|
|
|
logger.debug('Looking up repository')
|
2016-08-30 19:05:15 +00:00
|
|
|
repo = model.get_repository(namespace_name, repo_name)
|
2013-11-11 23:05:21 +00:00
|
|
|
if not repo:
|
2014-01-28 23:29:45 +00:00
|
|
|
abort(404, message='Unknown repository', issue='unknown-repo')
|
2013-11-11 23:05:21 +00:00
|
|
|
|
2015-02-11 19:15:18 +00:00
|
|
|
logger.debug('Building repository image response')
|
2015-03-04 21:36:32 +00:00
|
|
|
resp = make_response(json.dumps([]), 200)
|
2013-09-20 22:38:17 +00:00
|
|
|
resp.mimetype = 'application/json'
|
2013-09-20 15:55:44 +00:00
|
|
|
|
2015-09-15 19:23:06 +00:00
|
|
|
track_and_log('pull_repo', repo, analytics_name='pull_repo_100x', analytics_sample=0.01)
|
2016-11-03 19:28:40 +00:00
|
|
|
metric_queue.repository_pull.Inc(labelvalues=[namespace_name, repo_name, 'v1', True])
|
2013-09-20 22:38:17 +00:00
|
|
|
return resp
|
2013-09-20 15:55:44 +00:00
|
|
|
|
2013-11-11 23:05:21 +00:00
|
|
|
abort(403)
|
2013-09-20 15:55:44 +00:00
|
|
|
|
|
|
|
|
2016-01-21 20:40:51 +00:00
|
|
|
@v1_bp.route('/repositories/<repopath:repository>/images', methods=['DELETE'])
|
2013-09-20 22:38:17 +00:00
|
|
|
@process_auth
|
2016-03-09 21:20:28 +00:00
|
|
|
@parse_repository_name()
|
2015-02-19 21:54:23 +00:00
|
|
|
@generate_headers(scope=GrantType.WRITE_REPOSITORY)
|
2015-06-02 19:56:44 +00:00
|
|
|
@anon_allowed
|
2016-03-09 21:20:28 +00:00
|
|
|
def delete_repository_images(namespace_name, repo_name):
|
2014-01-28 23:29:45 +00:00
|
|
|
abort(501, 'Not Implemented', issue='not-implemented')
|
2013-09-20 15:55:44 +00:00
|
|
|
|
|
|
|
|
2016-01-21 20:40:51 +00:00
|
|
|
@v1_bp.route('/repositories/<repopath:repository>/auth', methods=['PUT'])
|
2016-03-09 21:20:28 +00:00
|
|
|
@parse_repository_name()
|
2015-06-02 19:56:44 +00:00
|
|
|
@anon_allowed
|
2016-03-09 21:20:28 +00:00
|
|
|
def put_repository_auth(namespace_name, repo_name):
|
2014-01-28 23:29:45 +00:00
|
|
|
abort(501, 'Not Implemented', issue='not-implemented')
|
2013-09-20 15:55:44 +00:00
|
|
|
|
|
|
|
|
2015-06-22 21:37:13 +00:00
|
|
|
@v1_bp.route('/search', methods=['GET'])
|
2014-08-22 23:41:22 +00:00
|
|
|
@process_auth
|
2015-05-19 21:52:44 +00:00
|
|
|
@anon_protect
|
2013-09-20 15:55:44 +00:00
|
|
|
def get_search():
|
2017-02-16 20:26:45 +00:00
|
|
|
query = request.args.get('q') or ''
|
2014-08-22 23:41:22 +00:00
|
|
|
|
2017-02-08 19:59:22 +00:00
|
|
|
try:
|
|
|
|
limit = min(100, max(1, int(request.args.get('n', 25))))
|
|
|
|
except ValueError:
|
|
|
|
limit = 25
|
|
|
|
|
|
|
|
try:
|
|
|
|
page = max(0, int(request.args.get('page', 1)))
|
|
|
|
except ValueError:
|
|
|
|
page = 1
|
|
|
|
|
2014-08-22 23:41:22 +00:00
|
|
|
username = None
|
|
|
|
user = get_authenticated_user()
|
|
|
|
if user is not None:
|
|
|
|
username = user.username
|
|
|
|
|
2017-02-08 19:59:22 +00:00
|
|
|
data = _conduct_repo_search(username, query, limit, page)
|
2014-08-22 23:41:22 +00:00
|
|
|
resp = make_response(json.dumps(data), 200)
|
|
|
|
resp.mimetype = 'application/json'
|
|
|
|
return resp
|
2016-08-19 18:00:21 +00:00
|
|
|
|
|
|
|
|
2017-02-08 19:59:22 +00:00
|
|
|
def _conduct_repo_search(username, query, limit=25, page=1):
|
2016-08-19 18:00:21 +00:00
|
|
|
""" Finds matching repositories. """
|
Optimize repository search by changing our lookup strategy
Previous to this change, repositories were looked up unfiltered in six different queries, and then filtered using the permissions model, which issued a query per repository found, making search incredibly slow. Instead, we now lookup a chunk of repositories unfiltered and then filter them via a single query to the database. By layering the filtering on top of the lookup, each as queries, we can minimize the number of queries necessary, without (at the same time) using a super expensive join.
Other changes:
- Remove the 5 page pre-lookup on V1 search and simply return that there is one more page available, until there isn't. While technically not correct, it is much more efficient, and no one should be using pagination with V1 search anyway.
- Remove the lookup for repos without entries in the RAC table. Instead, we now add a new RAC entry when the repository is created for *the day before*, with count 0, so that it is immediately searchable
- Remove lookup of results with a matching namespace; these aren't very relevant anyway, and it overly complicates sorting
2017-02-27 22:56:44 +00:00
|
|
|
# Note that we put a maximum limit of five pages here, because this API should only really ever
|
|
|
|
# be used by the Docker CLI, and it doesn't even paginate.
|
|
|
|
page = min(page, 5)
|
|
|
|
offset = (page - 1) * limit
|
2016-08-19 18:00:21 +00:00
|
|
|
|
2017-02-16 20:26:45 +00:00
|
|
|
if query:
|
Optimize repository search by changing our lookup strategy
Previous to this change, repositories were looked up unfiltered in six different queries, and then filtered using the permissions model, which issued a query per repository found, making search incredibly slow. Instead, we now lookup a chunk of repositories unfiltered and then filter them via a single query to the database. By layering the filtering on top of the lookup, each as queries, we can minimize the number of queries necessary, without (at the same time) using a super expensive join.
Other changes:
- Remove the 5 page pre-lookup on V1 search and simply return that there is one more page available, until there isn't. While technically not correct, it is much more efficient, and no one should be using pagination with V1 search anyway.
- Remove the lookup for repos without entries in the RAC table. Instead, we now add a new RAC entry when the repository is created for *the day before*, with count 0, so that it is immediately searchable
- Remove lookup of results with a matching namespace; these aren't very relevant anyway, and it overly complicates sorting
2017-02-27 22:56:44 +00:00
|
|
|
matching_repos = model.get_sorted_matching_repositories(query, username, limit=limit+1,
|
|
|
|
offset=offset)
|
2017-02-16 20:26:45 +00:00
|
|
|
else:
|
|
|
|
matching_repos = []
|
|
|
|
|
2017-02-08 19:59:22 +00:00
|
|
|
results = []
|
Optimize repository search by changing our lookup strategy
Previous to this change, repositories were looked up unfiltered in six different queries, and then filtered using the permissions model, which issued a query per repository found, making search incredibly slow. Instead, we now lookup a chunk of repositories unfiltered and then filter them via a single query to the database. By layering the filtering on top of the lookup, each as queries, we can minimize the number of queries necessary, without (at the same time) using a super expensive join.
Other changes:
- Remove the 5 page pre-lookup on V1 search and simply return that there is one more page available, until there isn't. While technically not correct, it is much more efficient, and no one should be using pagination with V1 search anyway.
- Remove the lookup for repos without entries in the RAC table. Instead, we now add a new RAC entry when the repository is created for *the day before*, with count 0, so that it is immediately searchable
- Remove lookup of results with a matching namespace; these aren't very relevant anyway, and it overly complicates sorting
2017-02-27 22:56:44 +00:00
|
|
|
for repo in matching_repos[0:limit]:
|
2016-08-19 18:00:21 +00:00
|
|
|
results.append({
|
|
|
|
'name': repo.namespace_name + '/' + repo.name,
|
|
|
|
'description': repo.description,
|
|
|
|
'is_public': repo.is_public,
|
|
|
|
'href': '/repository/' + repo.namespace_name + '/' + repo.name
|
|
|
|
})
|
2017-02-08 19:59:22 +00:00
|
|
|
|
|
|
|
# Defined: https://docs.docker.com/v1.6/reference/api/registry_api/
|
|
|
|
return {
|
|
|
|
'query': query,
|
|
|
|
'num_results': len(results),
|
Optimize repository search by changing our lookup strategy
Previous to this change, repositories were looked up unfiltered in six different queries, and then filtered using the permissions model, which issued a query per repository found, making search incredibly slow. Instead, we now lookup a chunk of repositories unfiltered and then filter them via a single query to the database. By layering the filtering on top of the lookup, each as queries, we can minimize the number of queries necessary, without (at the same time) using a super expensive join.
Other changes:
- Remove the 5 page pre-lookup on V1 search and simply return that there is one more page available, until there isn't. While technically not correct, it is much more efficient, and no one should be using pagination with V1 search anyway.
- Remove the lookup for repos without entries in the RAC table. Instead, we now add a new RAC entry when the repository is created for *the day before*, with count 0, so that it is immediately searchable
- Remove lookup of results with a matching namespace; these aren't very relevant anyway, and it overly complicates sorting
2017-02-27 22:56:44 +00:00
|
|
|
'num_pages': page + 1 if len(matching_repos) > limit else page,
|
2017-02-08 19:59:22 +00:00
|
|
|
'page': page,
|
|
|
|
'page_size': limit,
|
|
|
|
'results': results,
|
|
|
|
}
|