use kwargs for parse_repository_name

This commit is contained in:
Jimmy Zelinskie 2016-03-09 16:20:28 -05:00
parent 3b52a255b2
commit bb46cc933d
15 changed files with 285 additions and 270 deletions

View file

@ -1,8 +1,9 @@
import logging
import re
from jsonschema import validate, ValidationError
from functools import wraps
from jsonschema import validate, ValidationError
from flask import request, url_for
from flask.ext.principal import identity_changed, Identity
from cryptography.x509 import load_pem_x509_certificate

View file

@ -3,10 +3,11 @@
import json
import logging
from flask import request, url_for
from urllib import quote
from urlparse import urlunparse
from flask import request, url_for
from app import app
from buildtrigger.basehandler import BuildTriggerHandler
from buildtrigger.triggerutil import (TriggerDeactivationException,
@ -40,9 +41,9 @@ class BuildTriggerList(RepositoryParamResource):
@require_repo_admin
@nickname('listBuildTriggers')
def get(self, namespace, repository):
def get(self, namespace_name, repo_name):
""" List the triggers for the specified repository. """
triggers = model.build.list_build_triggers(namespace, repository)
triggers = model.build.list_build_triggers(namespace_name, repo_name)
return {
'triggers': [trigger_view(trigger, can_admin=True) for trigger in triggers]
}
@ -56,7 +57,7 @@ class BuildTrigger(RepositoryParamResource):
@require_repo_admin
@nickname('getBuildTrigger')
def get(self, namespace, repository, trigger_uuid):
def get(self, namespace_name, repo_name, trigger_uuid):
""" Get information for the specified build trigger. """
try:
trigger = model.build.get_build_trigger(trigger_uuid)
@ -67,7 +68,7 @@ class BuildTrigger(RepositoryParamResource):
@require_repo_admin
@nickname('deleteBuildTrigger')
def delete(self, namespace, repository, trigger_uuid):
def delete(self, namespace_name, repo_name, trigger_uuid):
""" Delete the specified build trigger. """
try:
trigger = model.build.get_build_trigger(trigger_uuid)
@ -82,10 +83,10 @@ class BuildTrigger(RepositoryParamResource):
# We are just going to eat this error
logger.warning('Trigger deactivation problem: %s', ex)
log_action('delete_repo_trigger', namespace,
{'repo': repository, 'trigger_id': trigger_uuid,
log_action('delete_repo_trigger', namespace_name,
{'repo': repo_name, 'trigger_id': trigger_uuid,
'service': trigger.service.name},
repo=model.repository.get_repository(namespace, repository))
repo=model.repository.get_repository(namespace_name, repo_name))
trigger.delete_instance(recursive=True)
@ -111,7 +112,7 @@ class BuildTriggerSubdirs(RepositoryParamResource):
@require_repo_admin
@nickname('listBuildTriggerSubdirs')
@validate_json_request('BuildTriggerSubdirRequest')
def post(self, namespace, repository, trigger_uuid):
def post(self, namespace_name, repo_name, trigger_uuid):
""" List the subdirectories available for the specified build trigger and source. """
try:
trigger = model.build.get_build_trigger(trigger_uuid)
@ -171,7 +172,7 @@ class BuildTriggerActivate(RepositoryParamResource):
@require_repo_admin
@nickname('activateBuildTrigger')
@validate_json_request('BuildTriggerActivateRequest')
def post(self, namespace, repository, trigger_uuid):
def post(self, namespace_name, repo_name, trigger_uuid):
""" Activate the specified build trigger. """
try:
trigger = model.build.get_build_trigger(trigger_uuid)
@ -198,7 +199,7 @@ class BuildTriggerActivate(RepositoryParamResource):
raise Unauthorized()
# Make sure the namespace matches that of the trigger.
if robot_namespace != namespace:
if robot_namespace != namespace_name:
raise Unauthorized()
# Set the pull robot.
@ -208,7 +209,7 @@ class BuildTriggerActivate(RepositoryParamResource):
new_config_dict = request.get_json()['config']
write_token_name = 'Build Trigger: %s' % trigger.service.name
write_token = model.token.create_delegate_token(namespace, repository, write_token_name,
write_token = model.token.create_delegate_token(namespace_name, repo_name, write_token_name,
'write')
try:
@ -233,12 +234,13 @@ class BuildTriggerActivate(RepositoryParamResource):
trigger.save()
# Log the trigger setup.
repo = model.repository.get_repository(namespace, repository)
log_action('setup_repo_trigger', namespace,
{'repo': repository, 'namespace': namespace,
'trigger_id': trigger.uuid, 'service': trigger.service.name,
'pull_robot': trigger.pull_robot.username if trigger.pull_robot else None,
'config': final_config}, repo=repo)
repo = model.repository.get_repository(namespace_name, repo_name)
log_action('setup_repo_trigger', namespace_name,
{'repo': repo_name, 'namespace': namespace_name,
'trigger_id': trigger.uuid, 'service': trigger.service.name,
'pull_robot': trigger.pull_robot.username if trigger.pull_robot else None,
'config': final_config},
repo=repo)
return trigger_view(trigger, can_admin=True)
else:
@ -271,7 +273,7 @@ class BuildTriggerAnalyze(RepositoryParamResource):
@require_repo_admin
@nickname('analyzeBuildTrigger')
@validate_json_request('BuildTriggerAnalyzeRequest')
def post(self, namespace, repository, trigger_uuid):
def post(self, namespace_name, repo_name, trigger_uuid):
""" Analyze the specified build trigger configuration. """
try:
trigger = model.build.get_build_trigger(trigger_uuid)
@ -414,7 +416,7 @@ class ActivateBuildTrigger(RepositoryParamResource):
@require_repo_admin
@nickname('manuallyStartBuildTrigger')
@validate_json_request('RunParameters')
def post(self, namespace, repository, trigger_uuid):
def post(self, namespace_name, repo_name, trigger_uuid):
""" Manually start a build from the specified trigger. """
try:
trigger = model.build.get_build_trigger(trigger_uuid)
@ -426,7 +428,7 @@ class ActivateBuildTrigger(RepositoryParamResource):
raise InvalidRequest('Trigger is not active.')
try:
repo = model.repository.get_repository(namespace, repository)
repo = model.repository.get_repository(namespace_name, repo_name)
pull_robot_name = model.build.get_pull_robot_name(trigger)
run_parameters = request.get_json()
@ -436,7 +438,7 @@ class ActivateBuildTrigger(RepositoryParamResource):
raise InvalidRequest(tse.message)
resp = build_status_view(build_request)
repo_string = '%s/%s' % (namespace, repository)
repo_string = '%s/%s' % (namespace_name, repo_name)
headers = {
'Location': api.url_for(RepositoryBuildStatus, repository=repo_string,
build_uuid=build_request.uuid),
@ -453,10 +455,10 @@ class TriggerBuildList(RepositoryParamResource):
@parse_args()
@query_param('limit', 'The maximum number of builds to return', type=int, default=5)
@nickname('listTriggerRecentBuilds')
def get(self, namespace, repository, trigger_uuid, parsed_args):
def get(self, namespace_name, repo_name, trigger_uuid, parsed_args):
""" List the builds started by the specified trigger. """
limit = parsed_args['limit']
builds = model.build.list_trigger_builds(namespace, repository, trigger_uuid, limit)
builds = model.build.list_trigger_builds(namespace_name, repo_name, trigger_uuid, limit)
return {
'builds': [build_status_view(bld) for bld in builds]
}
@ -470,7 +472,7 @@ class BuildTriggerFieldValues(RepositoryParamResource):
""" Custom verb to fetch a values list for a particular field name. """
@require_repo_admin
@nickname('listTriggerFieldValues')
def post(self, namespace, repository, trigger_uuid, field_name):
def post(self, namespace_name, repo_name, trigger_uuid, field_name):
""" List the field values for a custom run field. """
try:
trigger = model.build.get_build_trigger(trigger_uuid)
@ -501,7 +503,7 @@ class BuildTriggerSources(RepositoryParamResource):
""" Custom verb to fetch the list of build sources for the trigger config. """
@require_repo_admin
@nickname('listTriggerBuildSources')
def get(self, namespace, repository, trigger_uuid):
def get(self, namespace_name, repo_name, trigger_uuid):
""" List the build sources for the trigger configuration thus far. """
try:
trigger = model.build.get_build_trigger(trigger_uuid)

View file

@ -47,24 +47,28 @@ def get_cache_busters():
return CACHE_BUSTERS
def parse_repository_name(f):
@wraps(f)
def wrapper(repository, *args, **kwargs):
lib_namespace = app.config['LIBRARY_NAMESPACE']
(namespace, repository) = parse_namespace_repository(repository, lib_namespace)
return f(namespace, repository, *args, **kwargs)
return wrapper
def parse_repository_name(include_tag=False,
ns_kwarg_name='namespace_name',
repo_kwarg_name='repo_name',
tag_kwarg_name='tag_name',
incoming_repo_kwarg='repository'):
def inner(func):
@wraps(func)
def wrapper(*args, **kwargs):
parsed_stuff = parse_namespace_repository(kwargs[incoming_repo_kwarg],
app.config['LIBRARY_NAMESPACE'],
include_tag=include_tag)
del kwargs[incoming_repo_kwarg]
kwargs[ns_kwarg_name] = parsed_stuff[0]
kwargs[repo_kwarg_name] = parsed_stuff[1]
if include_tag:
kwargs[tag_kwarg_name] = parsed_stuff[2]
return func(*args, **kwargs)
return wrapper
return inner
def parse_repository_name_and_tag(f):
@wraps(f)
def wrapper(repository, *args, **kwargs):
lib_namespace = app.config['LIBRARY_NAMESPACE']
namespace, repository, tag = parse_namespace_repository(repository, lib_namespace,
include_tag=True)
return f(namespace, repository, tag, *args, **kwargs)
return wrapper
# TODO get rid of all calls to this parse_repository_name_and_tag
def route_show_if(value):
def decorator(f):

View file

@ -3,35 +3,37 @@ import logging
from flask import request, redirect, url_for, Blueprint
from flask.ext.login import current_user
from endpoints.common import route_show_if, parse_repository_name
from app import app, github_trigger
from data import model
from util.http import abort
from auth.permissions import AdministerRepositoryPermission
from auth.auth import require_session_login
import features
from app import app, github_trigger
from auth.auth import require_session_login
from auth.permissions import AdministerRepositoryPermission
from data import model
from endpoints.common import route_show_if, parse_repository_name
from util.http import abort
logger = logging.getLogger(__name__)
client = app.config['HTTPCLIENT']
githubtrigger = Blueprint('callback', __name__)
@githubtrigger.route('/github/callback/trigger/<repopath:repository>', methods=['GET'])
@route_show_if(features.GITHUB_BUILD)
@require_session_login
@parse_repository_name
def attach_github_build_trigger(namespace, repository):
permission = AdministerRepositoryPermission(namespace, repository)
@parse_repository_name()
def attach_github_build_trigger(namespace_name, repo_name):
permission = AdministerRepositoryPermission(namespace_name, repo_name)
if permission.can():
code = request.args.get('code')
token = github_trigger.exchange_code_for_token(app.config, client, code)
repo = model.repository.get_repository(namespace, repository)
repo = model.repository.get_repository(namespace_name, repo_name)
if not repo:
msg = 'Invalid repository: %s/%s' % (namespace, repository)
msg = 'Invalid repository: %s/%s' % (namespace_name, repo_name)
abort(404, message=msg)
trigger = model.build.create_build_trigger(repo, 'github', token, current_user.db_user())
repo_path = '%s/%s' % (namespace, repository)
repo_path = '%s/%s' % (namespace_name, repo_name)
full_url = '%s%s%s' % (url_for('web.repository', path=repo_path), '?tab=builds&newtrigger=',
trigger.uuid)

View file

@ -3,18 +3,18 @@ import requests
from flask import request, redirect, url_for, Blueprint
from flask.ext.login import current_user
from endpoints.common import common_login, route_show_if, parse_repository_name
from endpoints.web import render_page_template_with_routedata
from app import app, analytics, get_app_url, github_login, google_login, dex_login
from data import model
from util.validation import generate_valid_usernames
from util.http import abort
from auth.auth import require_session_login
from peewee import IntegrityError
import features
from app import app, analytics, get_app_url, github_login, google_login, dex_login
from auth.auth import require_session_login
from data import model
from endpoints.common import common_login, route_show_if
from endpoints.web import render_page_template_with_routedata
from util.security.strictjwt import decode, InvalidTokenError
from util.validation import generate_valid_usernames
logger = logging.getLogger(__name__)
client = app.config['HTTPCLIENT']
@ -24,10 +24,10 @@ def render_ologin_error(service_name,
error_message='Could not load user data. The token may have expired.'):
user_creation = features.USER_CREATION and features.DIRECT_LOGIN
return render_page_template_with_routedata('ologinerror.html',
service_name=service_name,
error_message=error_message,
service_url=get_app_url(),
user_creation=user_creation)
service_name=service_name,
error_message=error_message,
service_url=get_app_url(),
user_creation=user_creation)
def get_user(service, token):
@ -150,7 +150,7 @@ def github_oauth_callback():
# Retrieve the user's orgnizations (if organization filtering is turned on)
if github_login.allowed_organizations() is not None:
get_orgs = client.get(github_login.orgs_endpoint(), params=token_param,
headers={'Accept': 'application/vnd.github.moondragon+json'})
headers={'Accept': 'application/vnd.github.moondragon+json'})
organizations = set([org.get('login').lower() for org in get_orgs.json()])
if not (organizations & set(github_login.allowed_organizations())):
@ -271,8 +271,10 @@ def dex_oauth_callback():
payload = decode_user_jwt(token, dex_login)
except InvalidTokenError:
logger.exception('Exception when decoding returned JWT')
return render_ologin_error(dex_login.public_title,
'Could not decode response. Please contact your system administrator about this error.')
return render_ologin_error(
dex_login.public_title,
'Could not decode response. Please contact your system administrator about this error.',
)
username = get_email_username(payload)
metadata = {}
@ -281,9 +283,11 @@ def dex_oauth_callback():
email_address = payload['email']
if not payload.get('email_verified', False):
return render_ologin_error(dex_login.public_title,
return render_ologin_error(
dex_login.public_title,
'A verified e-mail address is required for login. Please verify your ' +
'e-mail address in %s and try again.' % dex_login.public_title)
'e-mail address in %s and try again.' % dex_login.public_title,
)
return conduct_oauth_login(dex_login, dex_id, username, email_address,
@ -304,8 +308,10 @@ def dex_oauth_attach():
payload = decode_user_jwt(token, dex_login)
except jwt.InvalidTokenError:
logger.exception('Exception when decoding returned JWT')
return render_ologin_error(dex_login.public_title,
'Could not decode response. Please contact your system administrator about this error.')
return render_ologin_error(
dex_login.public_title,
'Could not decode response. Please contact your system administrator about this error.',
)
user_obj = current_user.db_user()
dex_id = payload['sub']
@ -315,7 +321,7 @@ def dex_oauth_attach():
model.user.attach_federated_login(user_obj, 'dex', dex_id, metadata=metadata)
except IntegrityError:
err = '%s account is already attached to a %s account' % (dex_login.public_title,
app.config['REGISTRY_TITLE_SHORT'])
app.config['REGISTRY_TITLE_SHORT'])
return render_ologin_error(dex_login.public_title, err)
return redirect(url_for('web.user'))

View file

@ -2,19 +2,19 @@ import json
import logging
import urlparse
from flask import request, make_response, jsonify, session
from functools import wraps
from flask import request, make_response, jsonify, session
from data import model
from app import app, authentication, userevents, storage
from app import authentication, userevents
from auth.auth import process_auth, generate_signed_token
from auth.auth_context import get_authenticated_user, get_validated_token, get_validated_oauth_token
from util.names import REPOSITORY_NAME_REGEX
from auth.permissions import (ModifyRepositoryPermission, UserAdminPermission,
ReadRepositoryPermission, CreateRepositoryPermission,
repository_read_grant, repository_write_grant)
from util.http import abort
from util.names import REPOSITORY_NAME_REGEX
from endpoints.common import parse_repository_name
from endpoints.v1 import v1_bp
from endpoints.trackhelper import track_and_log
@ -33,12 +33,12 @@ class GrantType(object):
def generate_headers(scope=GrantType.READ_REPOSITORY, add_grant_for_status=None):
def decorator_method(f):
@wraps(f)
def wrapper(namespace, repository, *args, **kwargs):
response = f(namespace, repository, *args, **kwargs)
def wrapper(namespace_name, repo_name, *args, **kwargs):
response = f(namespace_name, repo_name, *args, **kwargs)
# Setting session namespace and repository
session['namespace'] = namespace
session['repository'] = repository
session['namespace'] = namespace_name
session['repository'] = repo_name
# We run our index and registry on the same hosts for now
registry_server = urlparse.urlparse(request.url).netloc
@ -51,11 +51,11 @@ def generate_headers(scope=GrantType.READ_REPOSITORY, add_grant_for_status=None)
grants = []
if scope == GrantType.READ_REPOSITORY:
if force_grant or ReadRepositoryPermission(namespace, repository).can():
grants.append(repository_read_grant(namespace, repository))
if force_grant or ReadRepositoryPermission(namespace_name, repo_name).can():
grants.append(repository_read_grant(namespace_name, repo_name))
elif scope == GrantType.WRITE_REPOSITORY:
if force_grant or ModifyRepositoryPermission(namespace, repository).can():
grants.append(repository_write_grant(namespace, repository))
if force_grant or ModifyRepositoryPermission(namespace_name, repo_name).can():
grants.append(repository_write_grant(namespace_name, repo_name))
# Generate a signed token for the user (if any) and the grants (if any)
if grants or get_authenticated_user():
@ -170,50 +170,50 @@ def update_user(username):
@v1_bp.route('/repositories/<repopath:repository>/', methods=['PUT'])
@process_auth
@parse_repository_name
@parse_repository_name()
@generate_headers(scope=GrantType.WRITE_REPOSITORY, add_grant_for_status=201)
@anon_allowed
def create_repository(namespace, repository):
def create_repository(namespace_name, repo_name):
# Verify that the repository name is valid.
if not REPOSITORY_NAME_REGEX.match(repository):
if not REPOSITORY_NAME_REGEX.match(repo_name):
abort(400, message='Invalid repository name. Repository names cannot contain slashes.')
logger.debug('Looking up repository %s/%s', namespace, repository)
repo = model.repository.get_repository(namespace, repository)
logger.debug('Looking up repository %s/%s', namespace_name, repo_name)
repo = model.repository.get_repository(namespace_name, repo_name)
logger.debug('Found repository %s/%s', namespace, repository)
logger.debug('Found repository %s/%s', namespace_name, repo_name)
if not repo and get_authenticated_user() is None:
logger.debug('Attempt to create repository %s/%s without user auth', namespace, repository)
logger.debug('Attempt to create repository %s/%s without user auth', namespace_name, repo_name)
abort(401,
message='Cannot create a repository as a guest. Please login via "docker login" first.',
issue='no-login')
elif repo:
permission = ModifyRepositoryPermission(namespace, repository)
permission = ModifyRepositoryPermission(namespace_name, repo_name)
if not permission.can():
abort(403,
message='You do not have permission to modify repository %(namespace)s/%(repository)s',
issue='no-repo-write-permission',
namespace=namespace, repository=repository)
namespace=namespace_name, repository=repo_name)
else:
permission = CreateRepositoryPermission(namespace)
permission = CreateRepositoryPermission(namespace_name)
if not permission.can():
logger.info('Attempt to create a new repo %s/%s with insufficient perms', namespace,
repository)
logger.info('Attempt to create a new repo %s/%s with insufficient perms', namespace_name,
repo_name)
msg = 'You do not have permission to create repositories in namespace "%(namespace)s"'
abort(403, message=msg, issue='no-create-permission', namespace=namespace)
abort(403, message=msg, issue='no-create-permission', namespace=namespace_name)
# Attempt to create the new repository.
logger.debug('Creating repository %s/%s with owner: %s', namespace, repository,
logger.debug('Creating repository %s/%s with owner: %s', namespace_name, repo_name,
get_authenticated_user().username)
repo = model.repository.create_repository(namespace, repository, get_authenticated_user())
repo = model.repository.create_repository(namespace_name, repo_name, get_authenticated_user())
if get_authenticated_user():
user_event_data = {
'action': 'push_start',
'repository': repository,
'namespace': namespace
'repository': repo_name,
'namespace': namespace_name,
}
event = userevents.get_event(get_authenticated_user().username)
@ -224,15 +224,15 @@ def create_repository(namespace, repository):
@v1_bp.route('/repositories/<repopath:repository>/images', methods=['PUT'])
@process_auth
@parse_repository_name
@parse_repository_name()
@generate_headers(scope=GrantType.WRITE_REPOSITORY)
@anon_allowed
def update_images(namespace, repository):
permission = ModifyRepositoryPermission(namespace, repository)
def update_images(namespace_name, repo_name):
permission = ModifyRepositoryPermission(namespace_name, repo_name)
if permission.can():
logger.debug('Looking up repository')
repo = model.repository.get_repository(namespace, repository)
repo = model.repository.get_repository(namespace_name, repo_name)
if not repo:
# Make sure the repo actually exists.
abort(404, message='Unknown repository', issue='unknown-repo')
@ -254,17 +254,17 @@ def update_images(namespace, repository):
@v1_bp.route('/repositories/<repopath:repository>/images', methods=['GET'])
@process_auth
@parse_repository_name
@parse_repository_name()
@generate_headers(scope=GrantType.READ_REPOSITORY)
@anon_protect
def get_repository_images(namespace, repository):
permission = ReadRepositoryPermission(namespace, repository)
def get_repository_images(namespace_name, repo_name):
permission = ReadRepositoryPermission(namespace_name, repo_name)
# TODO invalidate token?
if permission.can() or model.repository.repository_is_public(namespace, repository):
if permission.can() or model.repository.repository_is_public(namespace_name, repo_name):
# We can't rely on permissions to tell us if a repo exists anymore
logger.debug('Looking up repository')
repo = model.repository.get_repository(namespace, repository)
repo = model.repository.get_repository(namespace_name, repo_name)
if not repo:
abort(404, message='Unknown repository', issue='unknown-repo')
@ -280,17 +280,17 @@ def get_repository_images(namespace, repository):
@v1_bp.route('/repositories/<repopath:repository>/images', methods=['DELETE'])
@process_auth
@parse_repository_name
@parse_repository_name()
@generate_headers(scope=GrantType.WRITE_REPOSITORY)
@anon_allowed
def delete_repository_images(namespace, repository):
def delete_repository_images(namespace_name, repo_name):
abort(501, 'Not Implemented', issue='not-implemented')
@v1_bp.route('/repositories/<repopath:repository>/auth', methods=['PUT'])
@parse_repository_name
@parse_repository_name()
@anon_allowed
def put_repository_auth(namespace, repository):
def put_repository_auth(namespace_name, repo_name):
abort(501, 'Not Implemented', issue='not-implemented')

View file

@ -1,10 +1,8 @@
import logging
import json
from flask import abort, request, jsonify, make_response, session
from app import app
from util.names import TAG_ERROR, TAG_REGEX
from auth.auth import process_auth
from auth.permissions import (ReadRepositoryPermission,
@ -22,12 +20,12 @@ logger = logging.getLogger(__name__)
@v1_bp.route('/repositories/<repopath:repository>/tags', methods=['GET'])
@process_auth
@anon_protect
@parse_repository_name
def get_tags(namespace, repository):
permission = ReadRepositoryPermission(namespace, repository)
@parse_repository_name()
def get_tags(namespace_name, repo_name):
permission = ReadRepositoryPermission(namespace_name, repo_name)
if permission.can() or model.repository.repository_is_public(namespace, repository):
tags = model.tag.list_repository_tags(namespace, repository)
if permission.can() or model.repository.repository_is_public(namespace_name, repo_name):
tags = model.tag.list_repository_tags(namespace_name, repo_name)
tag_map = {tag.name: tag.image.docker_image_id for tag in tags}
return jsonify(tag_map)
@ -37,13 +35,13 @@ def get_tags(namespace, repository):
@v1_bp.route('/repositories/<repopath:repository>/tags/<tag>', methods=['GET'])
@process_auth
@anon_protect
@parse_repository_name
def get_tag(namespace, repository, tag):
permission = ReadRepositoryPermission(namespace, repository)
@parse_repository_name()
def get_tag(namespace_name, repo_name, tag):
permission = ReadRepositoryPermission(namespace_name, repo_name)
if permission.can() or model.repository.repository_is_public(namespace, repository):
if permission.can() or model.repository.repository_is_public(namespace_name, repo_name):
try:
tag_image = model.tag.get_tag_image(namespace, repository, tag)
tag_image = model.tag.get_tag_image(namespace_name, repo_name, tag)
except model.DataModelException:
abort(404)
@ -57,19 +55,19 @@ def get_tag(namespace, repository, tag):
@v1_bp.route('/repositories/<repopath:repository>/tags/<tag>', methods=['PUT'])
@process_auth
@anon_protect
@parse_repository_name
def put_tag(namespace, repository, tag):
permission = ModifyRepositoryPermission(namespace, repository)
@parse_repository_name()
def put_tag(namespace_name, repo_name, tag):
permission = ModifyRepositoryPermission(namespace_name, repo_name)
if permission.can():
if not TAG_REGEX.match(tag):
abort(400, TAG_ERROR)
docker_image_id = json.loads(request.data)
model.tag.create_or_update_tag(namespace, repository, tag, docker_image_id)
model.tag.create_or_update_tag(namespace_name, repo_name, tag, docker_image_id)
# Store the updated tag.
if not 'pushed_tags' in session:
if 'pushed_tags' not in session:
session['pushed_tags'] = {}
session['pushed_tags'][tag] = docker_image_id
@ -82,13 +80,13 @@ def put_tag(namespace, repository, tag):
@v1_bp.route('/repositories/<repopath:repository>/tags/<tag>', methods=['DELETE'])
@process_auth
@anon_protect
@parse_repository_name
def delete_tag(namespace, repository, tag):
permission = ModifyRepositoryPermission(namespace, repository)
@parse_repository_name()
def delete_tag(namespace_name, repo_name, tag):
permission = ModifyRepositoryPermission(namespace_name, repo_name)
if permission.can():
model.tag.delete_tag(namespace, repository, tag)
track_and_log('delete_tag', model.repository.get_repository(namespace, repository),
model.tag.delete_tag(namespace_name, repo_name, tag)
track_and_log('delete_tag', model.repository.get_repository(namespace_name, repo_name),
tag=tag)
return make_response('Deleted', 200)

View file

@ -41,13 +41,14 @@ def handle_registry_v2_exception(error):
def _require_repo_permission(permission_class, allow_public=False):
def wrapper(func):
@wraps(func)
def wrapped(namespace, repo_name, *args, **kwargs):
logger.debug('Checking permission %s for repo: %s/%s', permission_class, namespace, repo_name)
permission = permission_class(namespace, repo_name)
def wrapped(namespace_name, repo_name, *args, **kwargs):
logger.debug('Checking permission %s for repo: %s/%s', permission_class,
namespace_name, repo_name)
permission = permission_class(namespace_name, repo_name)
if (permission.can() or
(allow_public and
model.repository.repository_is_public(namespace, repo_name))):
return func(namespace, repo_name, *args, **kwargs)
model.repository.repository_is_public(namespace_name, repo_name))):
return func(namespace_name, repo_name, *args, **kwargs)
raise Unauthorized()
return wrapped
return wrapper

View file

@ -9,6 +9,7 @@ from app import storage, app
from auth.registry_jwt_auth import process_registry_jwt_auth
from data import model, database
from digest import digest_tools
from endpoints.common import parse_repository_name
from endpoints.v2 import v2_bp, require_repo_read, require_repo_write, get_input_stream
from endpoints.v2.errors import (BlobUnknown, BlobUploadInvalid, BlobUploadUnknown, Unsupported,
NameUnknown)
@ -17,7 +18,6 @@ from util.cache import cache_control
from util.registry.filelike import wrap_with_handler, StreamSlice
from util.registry.gzipstream import calculate_size_handler
from util.registry.torrent import PieceHasher
from endpoints.common import parse_repository_name
from storage.basestorage import InvalidChunkException
@ -34,12 +34,12 @@ class _InvalidRangeHeader(Exception):
pass
def _base_blob_fetch(namespace, repo_name, digest):
def _base_blob_fetch(namespace_name, repo_name, digest):
""" Some work that is common to both GET and HEAD requests. Callers MUST check for proper
authorization before calling this method.
"""
try:
found = model.blob.get_repo_blob_by_digest(namespace, repo_name, digest)
found = model.blob.get_repo_blob_by_digest(namespace_name, repo_name, digest)
except model.BlobDoesNotExist:
raise BlobUnknown()
@ -58,12 +58,12 @@ def _base_blob_fetch(namespace, repo_name, digest):
@v2_bp.route(BLOB_DIGEST_ROUTE, methods=['HEAD'])
@process_registry_jwt_auth
@parse_repository_name
@parse_repository_name()
@require_repo_read
@anon_protect
@cache_control(max_age=31436000)
def check_blob_exists(namespace, repo_name, digest):
found, headers = _base_blob_fetch(namespace, repo_name, digest)
def check_blob_exists(namespace_name, repo_name, digest):
found, headers = _base_blob_fetch(namespace_name, repo_name, digest)
response = make_response('')
response.headers.extend(headers)
@ -74,12 +74,12 @@ def check_blob_exists(namespace, repo_name, digest):
@v2_bp.route(BLOB_DIGEST_ROUTE, methods=['GET'])
@process_registry_jwt_auth
@parse_repository_name
@parse_repository_name()
@require_repo_read
@anon_protect
@cache_control(max_age=31536000)
def download_blob(namespace, repo_name, digest):
found, headers = _base_blob_fetch(namespace, repo_name, digest)
def download_blob(namespace_name, repo_name, digest):
found, headers = _base_blob_fetch(namespace_name, repo_name, digest)
path = model.storage.get_layer_path(found)
logger.debug('Looking up the direct download URL for path: %s', path)
@ -108,15 +108,15 @@ def _render_range(num_uploaded_bytes, with_bytes_prefix=True):
@v2_bp.route('/<repopath:repository>/blobs/uploads/', methods=['POST'])
@process_registry_jwt_auth
@parse_repository_name
@parse_repository_name()
@require_repo_write
@anon_protect
def start_blob_upload(namespace, repo_name):
def start_blob_upload(namespace_name, repo_name):
location_name = storage.preferred_locations[0]
new_upload_uuid, upload_metadata = storage.initiate_chunked_upload(location_name)
try:
model.blob.initiate_upload(namespace, repo_name, new_upload_uuid, location_name,
model.blob.initiate_upload(namespace_name, repo_name, new_upload_uuid, location_name,
upload_metadata)
except database.Repository.DoesNotExist:
raise NameUnknown()
@ -126,7 +126,7 @@ def start_blob_upload(namespace, repo_name):
# The user will send the blob data in another request
accepted = make_response('', 202)
accepted.headers['Location'] = url_for('v2.upload_chunk',
repository='%s/%s' % (namespace, repo_name),
repository='%s/%s' % (namespace_name, repo_name),
upload_uuid=new_upload_uuid)
accepted.headers['Range'] = _render_range(0)
@ -134,22 +134,22 @@ def start_blob_upload(namespace, repo_name):
return accepted
else:
# The user plans to send us the entire body right now
uploaded, error = _upload_chunk(namespace, repo_name, new_upload_uuid)
uploaded, error = _upload_chunk(namespace_name, repo_name, new_upload_uuid)
uploaded.save()
if error:
_range_not_satisfiable(uploaded.byte_count)
return _finish_upload(namespace, repo_name, uploaded, digest)
return _finish_upload(namespace_name, repo_name, uploaded, digest)
@v2_bp.route('/<repopath:repository>/blobs/uploads/<upload_uuid>', methods=['GET'])
@process_registry_jwt_auth
@parse_repository_name
@parse_repository_name()
@require_repo_write
@anon_protect
def fetch_existing_upload(namespace, repo_name, upload_uuid):
def fetch_existing_upload(namespace_name, repo_name, upload_uuid):
try:
found = model.blob.get_blob_upload(namespace, repo_name, upload_uuid)
found = model.blob.get_blob_upload(namespace_name, repo_name, upload_uuid)
except model.InvalidBlobUpload:
raise BlobUploadUnknown()
@ -189,12 +189,12 @@ def _parse_range_header(range_header_text):
return (start, length)
def _upload_chunk(namespace, repo_name, upload_uuid):
def _upload_chunk(namespace_name, repo_name, upload_uuid):
""" Common code among the various uploading paths for appending data to blobs.
Callers MUST call .save() or .delete_instance() on the returned database object.
"""
try:
found = model.blob.get_blob_upload(namespace, repo_name, upload_uuid)
found = model.blob.get_blob_upload(namespace_name, repo_name, upload_uuid)
except model.InvalidBlobUpload:
raise BlobUploadUnknown()
@ -280,7 +280,7 @@ def _upload_chunk(namespace, repo_name, upload_uuid):
return found, error
def _finish_upload(namespace, repo_name, upload_obj, expected_digest):
def _finish_upload(namespace_name, repo_name, upload_obj, expected_digest):
# Verify that the digest's SHA matches that of the uploaded data.
computed_digest = digest_tools.sha256_digest_from_hashlib(upload_obj.sha_state)
if not digest_tools.digests_equal(computed_digest, expected_digest):
@ -303,7 +303,7 @@ def _finish_upload(namespace, repo_name, upload_obj, expected_digest):
final_blob_location, upload_obj.storage_metadata)
# Mark the blob as uploaded.
blob_storage = model.blob.store_blob_record_and_temp_link(namespace, repo_name, expected_digest,
blob_storage = model.blob.store_blob_record_and_temp_link(namespace_name, repo_name, expected_digest,
upload_obj.location,
upload_obj.byte_count,
app.config['PUSH_TEMP_TAG_EXPIRATION_SEC'],
@ -319,18 +319,18 @@ def _finish_upload(namespace, repo_name, upload_obj, expected_digest):
response = make_response('', 201)
response.headers['Docker-Content-Digest'] = expected_digest
response.headers['Location'] = url_for('v2.download_blob',
repository='%s/%s' % (namespace, repo_name),
repository='%s/%s' % (namespace_name, repo_name),
digest=expected_digest)
return response
@v2_bp.route('/<repopath:repository>/blobs/uploads/<upload_uuid>', methods=['PATCH'])
@process_registry_jwt_auth
@parse_repository_name
@parse_repository_name()
@require_repo_write
@anon_protect
def upload_chunk(namespace, repo_name, upload_uuid):
upload, error = _upload_chunk(namespace, repo_name, upload_uuid)
def upload_chunk(namespace_name, repo_name, upload_uuid):
upload, error = _upload_chunk(namespace_name, repo_name, upload_uuid)
upload.save()
if error:
@ -345,31 +345,31 @@ def upload_chunk(namespace, repo_name, upload_uuid):
@v2_bp.route('/<repopath:repository>/blobs/uploads/<upload_uuid>', methods=['PUT'])
@process_registry_jwt_auth
@parse_repository_name
@parse_repository_name()
@require_repo_write
@anon_protect
def monolithic_upload_or_last_chunk(namespace, repo_name, upload_uuid):
def monolithic_upload_or_last_chunk(namespace_name, repo_name, upload_uuid):
digest = request.args.get('digest', None)
if digest is None:
raise BlobUploadInvalid()
found, error = _upload_chunk(namespace, repo_name, upload_uuid)
found, error = _upload_chunk(namespace_name, repo_name, upload_uuid)
if error:
found.save()
_range_not_satisfiable(found.byte_count)
return _finish_upload(namespace, repo_name, found, digest)
return _finish_upload(namespace_name, repo_name, found, digest)
@v2_bp.route('/<repopath:repository>/blobs/uploads/<upload_uuid>', methods=['DELETE'])
@parse_repository_name()
@process_registry_jwt_auth
@parse_repository_name
@require_repo_write
@anon_protect
def cancel_upload(namespace, repo_name, upload_uuid):
def cancel_upload(namespace_name, repo_name, upload_uuid):
try:
found = model.blob.get_blob_upload(namespace, repo_name, upload_uuid)
found = model.blob.get_blob_upload(namespace_name, repo_name, upload_uuid)
except model.InvalidBlobUpload:
raise BlobUploadUnknown()
@ -384,11 +384,9 @@ def cancel_upload(namespace, repo_name, upload_uuid):
@v2_bp.route('/<repopath:repository>/blobs/<digest>', methods=['DELETE'])
@process_registry_jwt_auth
@parse_repository_name
@parse_repository_name()
@require_repo_write
@anon_protect
def delete_digest(namespace, repo_name, upload_uuid):
def delete_digest(namespace_name, repo_name, upload_uuid):
# We do not support deleting arbitrary digests, as they break repo images.
raise Unsupported()

View file

@ -1,18 +1,22 @@
import logging
import jwt.utils
import json
import features
import hashlib
from peewee import IntegrityError
from flask import make_response, request, url_for
from collections import namedtuple, OrderedDict
from jwkest.jws import SIGNER_ALGS, keyrep
from datetime import datetime
from functools import wraps
import jwt.utils
from peewee import IntegrityError
from flask import make_response, request, url_for
from jwkest.jws import SIGNER_ALGS, keyrep
import features
from app import docker_v2_signing_key, app
from auth.registry_jwt_auth import process_registry_jwt_auth
from endpoints.common import parse_repository_name
from endpoints.decorators import anon_protect
from endpoints.v2 import v2_bp, require_repo_read, require_repo_write
from endpoints.v2.errors import (BlobUnknown, ManifestInvalid, ManifestUnknown, TagInvalid,
@ -22,7 +26,6 @@ from endpoints.notificationhelper import spawn_notification
from digest import digest_tools
from data import model
from data.database import RepositoryTag
from endpoints.common import parse_repository_name
logger = logging.getLogger(__name__)
@ -84,7 +87,8 @@ class SignedManifest(object):
def _validate(self):
for signature in self._signatures:
bytes_to_verify = '{0}.{1}'.format(signature['protected'], jwt.utils.base64url_encode(self.payload))
bytes_to_verify = '{0}.{1}'.format(signature['protected'],
jwt.utils.base64url_encode(self.payload))
signer = SIGNER_ALGS[signature['header']['alg']]
key = keyrep(signature['header']['jwk'])
gk = key.get_key()
@ -163,9 +167,9 @@ class SignedManifest(object):
class SignedManifestBuilder(object):
""" Class which represents a manifest which is currently being built.
"""
def __init__(self, namespace, repo_name, tag, architecture='amd64', schema_ver=1):
repo_name_key = '{0}/{1}'.format(namespace, repo_name)
if namespace == '':
def __init__(self, namespace_name, repo_name, tag, architecture='amd64', schema_ver=1):
repo_name_key = '{0}/{1}'.format(namespace_name, repo_name)
if namespace_name == '':
repo_name_key = repo_name
self._base_payload = {
@ -238,26 +242,26 @@ class SignedManifestBuilder(object):
@v2_bp.route(MANIFEST_TAGNAME_ROUTE, methods=['GET'])
@process_registry_jwt_auth
@parse_repository_name
@parse_repository_name()
@require_repo_read
@anon_protect
def fetch_manifest_by_tagname(namespace, repo_name, manifest_ref):
def fetch_manifest_by_tagname(namespace_name, repo_name, manifest_ref):
try:
manifest = model.tag.load_tag_manifest(namespace, repo_name, manifest_ref)
manifest = model.tag.load_tag_manifest(namespace_name, repo_name, manifest_ref)
except model.InvalidManifestException:
try:
model.tag.get_active_tag(namespace, repo_name, manifest_ref)
model.tag.get_active_tag(namespace_name, repo_name, manifest_ref)
except RepositoryTag.DoesNotExist:
raise ManifestUnknown()
raise ManifestUnknown()
try:
manifest = _generate_and_store_manifest(namespace, repo_name, manifest_ref)
manifest = _generate_and_store_manifest(namespace_name, repo_name, manifest_ref)
except model.DataModelException:
logger.exception('Exception when generating manifest for %s/%s:%s', namespace, repo_name,
logger.exception('Exception when generating manifest for %s/%s:%s', namespace_name, repo_name,
manifest_ref)
raise ManifestUnknown()
repo = model.repository.get_repository(namespace, repo_name)
repo = model.repository.get_repository(namespace_name, repo_name)
if repo is not None:
track_and_log('pull_repo', repo, analytics_name='pull_repo_100x', analytics_sample=0.01)
@ -269,17 +273,17 @@ def fetch_manifest_by_tagname(namespace, repo_name, manifest_ref):
@v2_bp.route(MANIFEST_DIGEST_ROUTE, methods=['GET'])
@process_registry_jwt_auth
@parse_repository_name
@parse_repository_name()
@require_repo_read
@anon_protect
def fetch_manifest_by_digest(namespace, repo_name, manifest_ref):
def fetch_manifest_by_digest(namespace_name, repo_name, manifest_ref):
try:
manifest = model.tag.load_manifest_by_digest(namespace, repo_name, manifest_ref)
manifest = model.tag.load_manifest_by_digest(namespace_name, repo_name, manifest_ref)
except model.InvalidManifestException:
# Without a tag name to reference, we can't make an attempt to generate the manifest
raise ManifestUnknown()
repo = model.repository.get_repository(namespace, repo_name)
repo = model.repository.get_repository(namespace_name, repo_name)
if repo is not None:
track_and_log('pull_repo', repo)
@ -301,11 +305,11 @@ def _reject_manifest2_schema2(func):
@v2_bp.route(MANIFEST_TAGNAME_ROUTE, methods=['PUT'])
@process_registry_jwt_auth
@parse_repository_name
@parse_repository_name()
@require_repo_write
@anon_protect
@_reject_manifest2_schema2
def write_manifest_by_tagname(namespace, repo_name, manifest_ref):
def write_manifest_by_tagname(namespace_name, repo_name, manifest_ref):
try:
manifest = SignedManifest(request.data)
except ValueError:
@ -314,16 +318,16 @@ def write_manifest_by_tagname(namespace, repo_name, manifest_ref):
if manifest.tag != manifest_ref:
raise TagInvalid()
return _write_manifest(namespace, repo_name, manifest)
return _write_manifest(namespace_name, repo_name, manifest)
@v2_bp.route(MANIFEST_DIGEST_ROUTE, methods=['PUT'])
@process_registry_jwt_auth
@parse_repository_name
@parse_repository_name()
@require_repo_write
@anon_protect
@_reject_manifest2_schema2
def write_manifest_by_digest(namespace, repo_name, manifest_ref):
def write_manifest_by_digest(namespace_name, repo_name, manifest_ref):
try:
manifest = SignedManifest(request.data)
except ValueError:
@ -332,7 +336,7 @@ def write_manifest_by_digest(namespace, repo_name, manifest_ref):
if manifest.digest != manifest_ref:
raise ManifestInvalid(detail={'message': 'manifest digest mismatch'})
return _write_manifest(namespace, repo_name, manifest)
return _write_manifest(namespace_name, repo_name, manifest)
def _updated_v1_metadata(v1_metadata_json, updated_id_map):
@ -350,21 +354,21 @@ def _updated_v1_metadata(v1_metadata_json, updated_id_map):
return json.dumps(parsed)
def _write_manifest(namespace, repo_name, manifest):
def _write_manifest(namespace_name, repo_name, manifest):
# Ensure that the manifest is for this repository. If the manifest's namespace is empty, then
# it is for the library namespace and we need an extra check.
if (manifest.namespace == '' and features.LIBRARY_SUPPORT and
namespace == app.config['LIBRARY_NAMESPACE']):
namespace_name == app.config['LIBRARY_NAMESPACE']):
# This is a library manifest. All good.
pass
elif manifest.namespace != namespace:
elif manifest.namespace != namespace_name:
raise NameInvalid()
if manifest.repo_name != repo_name:
raise NameInvalid()
# Ensure that the repository exists.
repo = model.repository.get_repository(namespace, repo_name)
repo = model.repository.get_repository(namespace_name, repo_name)
if repo is None:
raise NameInvalid()
@ -411,8 +415,8 @@ def _write_manifest(namespace, repo_name, manifest):
v1_metadata_str = mdata.v1_metadata_str.encode('utf-8')
working_docker_id = hashlib.sha256(v1_metadata_str + '@' + digest_str).hexdigest()
logger.debug('Rewriting docker_id %s/%s %s -> %s', namespace, repo_name, v1_mdata.docker_id,
working_docker_id)
logger.debug('Rewriting docker_id %s/%s %s -> %s', namespace_name, repo_name,
v1_mdata.docker_id, working_docker_id)
has_rewritten_ids = True
# Store the new docker id in the map
@ -447,7 +451,7 @@ def _write_manifest(namespace, repo_name, manifest):
# Store the manifest pointing to the tag.
manifest_digest = manifest.digest
leaf_layer_id = images_map[layers[-1].v1_metadata.docker_id].docker_image_id
model.tag.store_tag_manifest(namespace, repo_name, tag_name, leaf_layer_id, manifest_digest,
model.tag.store_tag_manifest(namespace_name, repo_name, tag_name, leaf_layer_id, manifest_digest,
manifest.bytes)
# Spawn the repo_push event.
@ -461,29 +465,29 @@ def _write_manifest(namespace, repo_name, manifest):
response = make_response('OK', 202)
response.headers['Docker-Content-Digest'] = manifest_digest
response.headers['Location'] = url_for('v2.fetch_manifest_by_digest',
repository='%s/%s' % (namespace, repo_name),
repository='%s/%s' % (namespace_name, repo_name),
manifest_ref=manifest_digest)
return response
@v2_bp.route(MANIFEST_DIGEST_ROUTE, methods=['DELETE'])
@process_registry_jwt_auth
@parse_repository_name
@parse_repository_name()
@require_repo_write
@anon_protect
def delete_manifest_by_digest(namespace, repo_name, manifest_ref):
def delete_manifest_by_digest(namespace_name, repo_name, manifest_ref):
""" Delete the manifest specified by the digest. Note: there is no equivalent
method for deleting by tag name because it is forbidden by the spec.
"""
try:
manifest = model.tag.load_manifest_by_digest(namespace, repo_name, manifest_ref)
manifest = model.tag.load_manifest_by_digest(namespace_name, repo_name, manifest_ref)
except model.InvalidManifestException:
# Without a tag name to reference, we can't make an attempt to generate the manifest
raise ManifestUnknown()
# Mark the tag as no longer alive.
try:
model.tag.delete_tag(namespace, repo_name, manifest.tag.name)
model.tag.delete_tag(namespace_name, repo_name, manifest.tag.name)
except model.DataModelException:
# Tag is not alive.
raise ManifestUnknown()
@ -494,15 +498,15 @@ def delete_manifest_by_digest(namespace, repo_name, manifest_ref):
return make_response('', 202)
def _generate_and_store_manifest(namespace, repo_name, tag_name):
def _generate_and_store_manifest(namespace_name, repo_name, tag_name):
# First look up the tag object and its ancestors
image = model.tag.get_tag_image(namespace, repo_name, tag_name)
parents = model.image.get_parent_images(namespace, repo_name, image)
image = model.tag.get_tag_image(namespace_name, repo_name, tag_name)
parents = model.image.get_parent_images(namespace_name, repo_name, image)
# If the manifest is being generated under the library namespace, then we make its namespace
# empty.
manifest_namespace = namespace
if features.LIBRARY_SUPPORT and namespace == app.config['LIBRARY_NAMESPACE']:
manifest_namespace = namespace_name
if features.LIBRARY_SUPPORT and namespace_name == app.config['LIBRARY_NAMESPACE']:
manifest_namespace = ''
# Create and populate the manifest builder
@ -520,13 +524,12 @@ def _generate_and_store_manifest(namespace, repo_name, tag_name):
# Write the manifest to the DB. If an existing manifest already exists, return the
# one found.
try:
return model.tag.associate_generated_tag_manifest(namespace, repo_name, tag_name,
return model.tag.associate_generated_tag_manifest(namespace_name, repo_name, tag_name,
manifest.digest, manifest.bytes)
except IntegrityError as ie:
logger.debug('Got integrity error: %s', ie)
try:
return model.tag.load_tag_manifest(namespace, repo_name, tag_name)
return model.tag.load_tag_manifest(namespace_name, repo_name, tag_name)
except model.InvalidManifestException:
logger.exception('Exception when generating manifest')
raise model.DataModelException('Could not load or generate manifest')

View file

@ -1,29 +1,29 @@
from flask import jsonify, url_for
from auth.registry_jwt_auth import process_registry_jwt_auth
from endpoints.common import parse_repository_name
from endpoints.v2 import v2_bp, require_repo_read
from endpoints.v2.errors import NameUnknown
from endpoints.v2.v2util import add_pagination
from endpoints.decorators import anon_protect
from data import model
from endpoints.common import parse_repository_name
@v2_bp.route('/<repopath:repository>/tags/list', methods=['GET'])
@process_registry_jwt_auth
@parse_repository_name
@parse_repository_name()
@require_repo_read
@anon_protect
def list_all_tags(namespace, repo_name):
repository = model.repository.get_repository(namespace, repo_name)
def list_all_tags(namespace_name, repo_name):
repository = model.repository.get_repository(namespace_name, repo_name)
if repository is None:
raise NameUnknown()
query = model.tag.list_repository_tags(namespace, repo_name)
url = url_for('v2.list_all_tags', repository='%s/%s' % (namespace, repo_name))
query = model.tag.list_repository_tags(namespace_name, repo_name)
url = url_for('v2.list_all_tags', repository='%s/%s' % (namespace_name, repo_name))
link, query = add_pagination(query, url)
response = jsonify({
'name': '{0}/{1}'.format(namespace, repo_name),
'name': '{0}/{1}'.format(namespace_name, repo_name),
'tags': [tag.name for tag in query],
})

View file

@ -381,10 +381,10 @@ def get_squashed_tag(namespace, repository, tag):
@anon_protect
@verbs.route('/torrent{0}'.format(BLOB_DIGEST_ROUTE), methods=['GET'])
@process_auth
@parse_repository_name
def get_tag_torrent(namespace, repo_name, digest):
permission = ReadRepositoryPermission(namespace, repo_name)
public_repo = model.repository.repository_is_public(namespace, repo_name)
@parse_repository_name()
def get_tag_torrent(namespace_name, repo_name, digest):
permission = ReadRepositoryPermission(namespace_name, repo_name)
public_repo = model.repository.repository_is_public(namespace_name, repo_name)
if not permission.can() and not public_repo:
abort(403)
@ -394,7 +394,7 @@ def get_tag_torrent(namespace, repo_name, digest):
abort(403)
try:
blob = model.blob.get_repo_blob_by_digest(namespace, repo_name, digest)
blob = model.blob.get_repo_blob_by_digest(namespace_name, repo_name, digest)
except model.BlobDoesNotExist:
abort(404)

View file

@ -26,7 +26,7 @@ from data import model
from data.database import db
from endpoints.api.discovery import swagger_route_data
from endpoints.common import (common_login, render_page_template, route_show_if, param_required,
parse_repository_name, parse_repository_name_and_tag)
parse_repository_name)
from endpoints.csrf import csrf_protect, generate_csrf_token, verify_csrf
from endpoints.decorators import anon_protect, anon_allowed
from health.healthcheck import get_healthchecker
@ -411,20 +411,20 @@ def confirm_recovery():
@web.route('/repository/<repopath:repository>/status', methods=['GET'])
@parse_repository_name
@parse_repository_name()
@anon_protect
def build_status_badge(namespace, repository):
def build_status_badge(namespace_name, repo_name):
token = request.args.get('token', None)
is_public = model.repository.repository_is_public(namespace, repository)
is_public = model.repository.repository_is_public(namespace_name, repo_name)
if not is_public:
repo = model.repository.get_repository(namespace, repository)
repo = model.repository.get_repository(namespace_name, repo_name)
if not repo or token != repo.badge_token:
abort(404)
# Lookup the tags for the repository.
tags = model.tag.list_repository_tags(namespace, repository)
tags = model.tag.list_repository_tags(namespace_name, repo_name)
is_empty = len(list(tags)) == 0
recent_build = model.build.get_recent_repository_build(namespace, repository)
recent_build = model.build.get_recent_repository_build(namespace_name, repo_name)
if not is_empty and (not recent_build or recent_build.phase == 'complete'):
status_name = 'ready'
@ -600,14 +600,14 @@ def download_logs_archive():
@web.route('/bitbucket/setup/<repopath:repository>', methods=['GET'])
@require_session_login
@parse_repository_name
@parse_repository_name()
@route_show_if(features.BITBUCKET_BUILD)
def attach_bitbucket_trigger(namespace, repository_name):
permission = AdministerRepositoryPermission(namespace, repository_name)
def attach_bitbucket_trigger(namespace_name, repo_name):
permission = AdministerRepositoryPermission(namespace_name, repo_name)
if permission.can():
repo = model.repository.get_repository(namespace, repository_name)
repo = model.repository.get_repository(namespace_name, repo_name)
if not repo:
msg = 'Invalid repository: %s/%s' % (namespace, repository_name)
msg = 'Invalid repository: %s/%s' % (namespace_name, repo_name)
abort(404, message=msg)
trigger = model.build.create_build_trigger(repo, BitbucketBuildTrigger.service_name(), None,
@ -634,19 +634,19 @@ def attach_bitbucket_trigger(namespace, repository_name):
@web.route('/customtrigger/setup/<repopath:repository>', methods=['GET'])
@require_session_login
@parse_repository_name
def attach_custom_build_trigger(namespace, repository_name):
permission = AdministerRepositoryPermission(namespace, repository_name)
@parse_repository_name()
def attach_custom_build_trigger(namespace_name, repo_name):
permission = AdministerRepositoryPermission(namespace_name, repo_name)
if permission.can():
repo = model.repository.get_repository(namespace, repository_name)
repo = model.repository.get_repository(namespace_name, repo_name)
if not repo:
msg = 'Invalid repository: %s/%s' % (namespace, repository_name)
msg = 'Invalid repository: %s/%s' % (namespace_name, repo_name)
abort(404, message=msg)
trigger = model.build.create_build_trigger(repo, CustomBuildTrigger.service_name(),
None, current_user.db_user())
repo_path = '%s/%s' % (namespace, repository_name)
repo_path = '%s/%s' % (namespace_name, repo_name)
full_url = '%s%s%s' % (url_for('web.repository', path=repo_path), '?tab=builds&newtrigger=',
trigger.uuid)
@ -655,21 +655,22 @@ def attach_custom_build_trigger(namespace, repository_name):
abort(403)
@web.route('/<repopath:repository>')
@no_cache
@process_oauth
@parse_repository_name_and_tag
@parse_repository_name(include_tag=True)
@anon_protect
def redirect_to_repository(namespace, reponame, tag_name):
permission = ReadRepositoryPermission(namespace, reponame)
is_public = model.repository.repository_is_public(namespace, reponame)
def redirect_to_repository(namespace_name, repo_name, tag_name):
permission = ReadRepositoryPermission(namespace_name, repo_name)
is_public = model.repository.repository_is_public(namespace_name, repo_name)
if request.args.get('ac-discovery', 0) == 1:
return index('')
if permission.can() or is_public:
repository_name = '/'.join([namespace, reponame])
return redirect(url_for('web.repository', path=repository_name, tag=tag_name))
repo_path = '/'.join([namespace_name, repo_name])
return redirect(url_for('web.repository', path=repo_path, tag=tag_name))
abort(404)

View file

@ -5,4 +5,4 @@ export TROLLIUSDEBUG=1
python -m unittest discover -f
python -m test.registry_tests -f
python -m test.queue_threads -f
#python -m test.queue_threads -f

View file

@ -1,10 +1,9 @@
import unittest
import endpoints.decorated
import json
from app import app
from initdb import setup_database_for_testing, finished_database_for_testing
from specs import build_v2_index_specs
from test.specs import build_v2_index_specs
from endpoints.v2 import v2_bp
app.register_blueprint(v2_bp, url_prefix='/v2')