Holy black magic batman, move the query parameters to decorators and expose them through discovery.

This commit is contained in:
jakedt 2014-03-11 12:57:33 -04:00
parent b3e0dfae48
commit 978d68f0e0
5 changed files with 209 additions and 135 deletions

View file

@ -9,7 +9,7 @@ application.config['LOGGING_CONFIG']()
# Turn off debug logging for boto # Turn off debug logging for boto
logging.getLogger('boto').setLevel(logging.CRITICAL) logging.getLogger('boto').setLevel(logging.CRITICAL)
from endpoints.api import api from endpoints.api import api_bp
from endpoints.index import index from endpoints.index import index
from endpoints.web import web from endpoints.web import web
from endpoints.tags import tags from endpoints.tags import tags
@ -26,7 +26,7 @@ application.register_blueprint(callback, url_prefix='/oauth2')
application.register_blueprint(index, url_prefix='/v1') application.register_blueprint(index, url_prefix='/v1')
application.register_blueprint(tags, url_prefix='/v1') application.register_blueprint(tags, url_prefix='/v1')
application.register_blueprint(registry, url_prefix='/v1') application.register_blueprint(registry, url_prefix='/v1')
application.register_blueprint(api, url_prefix='/api') application.register_blueprint(api_bp, url_prefix='/api')
application.register_blueprint(webhooks, url_prefix='/webhooks') application.register_blueprint(webhooks, url_prefix='/webhooks')
application.register_blueprint(realtime, url_prefix='/realtime') application.register_blueprint(realtime, url_prefix='/realtime')

View file

@ -1,5 +1,8 @@
import logging
from flask import Blueprint, request from flask import Blueprint, request
from flask.ext.restful import Resource, abort from flask.ext.restful import Resource, abort, Api, reqparse
from flask.ext.restful.utils.cors import crossdomain
from flask.ext.login import current_user from flask.ext.login import current_user
from calendar import timegm from calendar import timegm
from email.utils import formatdate from email.utils import formatdate
@ -13,8 +16,17 @@ from auth.permissions import (ReadRepositoryPermission,
AdministerRepositoryPermission) AdministerRepositoryPermission)
logger = logging.getLogger(__name__)
api_bp = Blueprint('api', __name__)
api = Api(api_bp)
api.decorators = [crossdomain(origin='*')]
api = Blueprint('api', __name__)
def resource(*urls, **kwargs):
def wrapper(api_resource):
api.add_resource(api_resource, *urls, **kwargs)
return api_resource
return wrapper
def truthy_bool(param): def truthy_bool(param):
@ -44,6 +56,41 @@ def method_metadata(func, name):
nickname = partial(add_method_metadata, 'nickname') nickname = partial(add_method_metadata, 'nickname')
def query_parameter(name, help_str, type=reqparse.text_type,
default=None, choices=(), required=False):
def add_param(func):
logger.debug('%s', func)
if '__api_query_params' not in dir(func):
func.__api_query_params = []
func.__api_query_params.append({
'name': name,
'type': type,
'help': help_str,
'default': default,
'choices': choices,
'required': required,
})
return func
return add_param
def parse_args(func):
@wraps(func)
def wrapper(self, *args, **kwargs):
if '__api_query_params' not in dir(func):
logger.debug('No query params defined.')
logger.debug('%s', func)
abort(400)
parser = reqparse.RequestParser()
for arg_spec in func.__api_query_params:
parser.add_argument(**arg_spec)
parsed_args = parser.parse_args()
return func(self, parsed_args, *args, **kwargs)
return wrapper
def parse_repository_name(func): def parse_repository_name(func):
@wraps(func) @wraps(func)
def wrapper(repository, *args, **kwargs): def wrapper(repository, *args, **kwargs):

View file

@ -1,17 +1,26 @@
import re import re
import logging
from flask.ext.restful import Api, Resource from flask.ext.restful import Resource, reqparse
from flask.ext.restful.utils.cors import crossdomain
from endpoints.api import api, method_metadata, nickname from endpoints.api import resource, method_metadata, nickname, truthy_bool
from endpoints.common import get_route_data
from app import app from app import app
discovery_api = Api(api) logger = logging.getLogger(__name__)
discovery_api.decorators = [crossdomain(origin='*')]
PARAM_REGEX = re.compile(r'<([\w]+:)?([\w]+)>')
TYPE_CONVERTER = {
truthy_bool: 'boolean',
str: 'string',
basestring: 'string',
reqparse.text_type: 'string',
int: 'integer',
}
param_regex = re.compile(r'<([\w]+:)?([\w]+)>')
def swagger_route_data(): def swagger_route_data():
apis = [] apis = []
@ -49,6 +58,22 @@ def swagger_route_data():
schema = endpoint_method.view_class.schemas[req_schema_name] schema = endpoint_method.view_class.schemas[req_schema_name]
models[req_schema_name] = schema models[req_schema_name] = schema
logger.debug('method dir: %s', dir(method))
if '__api_query_params' in dir(method):
for param_spec in method.__api_query_params:
new_param = {
'paramType': 'query',
'name': param_spec['name'],
'description': param_spec['help'],
'dataType': TYPE_CONVERTER[param_spec['type']],
'required': param_spec['required'],
}
if len(param_spec['choices']) > 0:
new_param['enum'] = list(param_spec['choices'])
parameters.append(new_param)
if method is not None: if method is not None:
operations.append({ operations.append({
'method': method_name, 'method': method_name,
@ -57,7 +82,7 @@ def swagger_route_data():
'parameters': parameters, 'parameters': parameters,
}) })
swagger_path = param_regex.sub(r'{\2}', rule.rule) swagger_path = PARAM_REGEX.sub(r'{\2}', rule.rule)
apis.append({ apis.append({
'path': swagger_path, 'path': swagger_path,
'description': 'Resource description.', 'description': 'Resource description.',
@ -67,18 +92,24 @@ def swagger_route_data():
swagger_data = { swagger_data = {
'apiVersion': 'v1', 'apiVersion': 'v1',
'swaggerVersion': '1.2', 'swaggerVersion': '1.2',
'basePath': 'https://quay.io/', 'basePath': 'http://ci.devtable.com:5000',
'resourcePath': '/', 'resourcePath': '/',
'info': {
'title': 'Quay.io API',
'description': ('This API allows you to perform many of the operations '
'required to work with Quay.io repositories, users, and '
'organizations. You can find out more at '
'<a href="https://quay.io">Quay.io</a>.'),
'termsOfServiceUrl': 'https://quay.io/tos',
'contact': 'support@quay.io',
},
'apis': apis, 'apis': apis,
'models': models, 'models': models,
} }
return swagger_data return swagger_data
@resource('/v1/discovery')
class DiscoveryResource(Resource): class DiscoveryResource(Resource):
@nickname('discovery') @nickname('discovery')
def get(self): def get(self):
return swagger_route_data() return swagger_route_data()
discovery_api.add_resource(DiscoveryResource, '/v1/discovery')

View file

@ -11,7 +11,7 @@ from functools import wraps
from collections import defaultdict from collections import defaultdict
from urllib import quote from urllib import quote
from endpoints.api import api from endpoints.api import api_bp
from data import model from data import model
from data.plans import PLANS, get_plan from data.plans import PLANS, get_plan
from app import app from app import app
@ -43,7 +43,7 @@ build_logs = app.config['BUILDLOGS']
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@api.before_request @api_bp.before_request
def csrf_protect(): def csrf_protect():
if request.method != "GET" and request.method != "HEAD": if request.method != "GET" and request.method != "HEAD":
token = session.get('_csrf_token', None) token = session.get('_csrf_token', None)
@ -111,18 +111,18 @@ def org_api_call(user_call_name):
return internal_decorator return internal_decorator
@api.route('/discovery') @api_bp.route('/discovery')
def discovery(): def discovery():
return jsonify(get_route_data()) return jsonify(get_route_data())
@api.route('/') @api_bp.route('/')
@internal_api_call @internal_api_call
def welcome(): def welcome():
return jsonify({'version': '0.5'}) return jsonify({'version': '0.5'})
@api.route('/plans/') @api_bp.route('/plans/')
def list_plans(): def list_plans():
return jsonify({ return jsonify({
'plans': PLANS, 'plans': PLANS,
@ -165,7 +165,7 @@ def user_view(user):
} }
@api.route('/user/', methods=['GET']) @api_bp.route('/user/', methods=['GET'])
@internal_api_call @internal_api_call
def get_logged_in_user(): def get_logged_in_user():
if current_user.is_anonymous(): if current_user.is_anonymous():
@ -178,7 +178,7 @@ def get_logged_in_user():
return jsonify(user_view(user)) return jsonify(user_view(user))
@api.route('/user/private', methods=['GET']) @api_bp.route('/user/private', methods=['GET'])
@api_login_required @api_login_required
@internal_api_call @internal_api_call
def get_user_private_allowed(): def get_user_private_allowed():
@ -199,7 +199,7 @@ def get_user_private_allowed():
}) })
@api.route('/user/convert', methods=['POST']) @api_bp.route('/user/convert', methods=['POST'])
@api_login_required @api_login_required
@internal_api_call @internal_api_call
def convert_user_to_organization(): def convert_user_to_organization():
@ -230,7 +230,7 @@ def convert_user_to_organization():
return conduct_signin(admin_username, admin_password) return conduct_signin(admin_username, admin_password)
@api.route('/user/', methods=['PUT']) @api_bp.route('/user/', methods=['PUT'])
@api_login_required @api_login_required
@internal_api_call @internal_api_call
def change_user_details(): def change_user_details():
@ -264,7 +264,7 @@ def change_user_details():
return jsonify(user_view(user)) return jsonify(user_view(user))
@api.route('/user/', methods=['POST']) @api_bp.route('/user/', methods=['POST'])
@internal_api_call @internal_api_call
def create_new_user(): def create_new_user():
user_data = request.get_json() user_data = request.get_json()
@ -283,7 +283,7 @@ def create_new_user():
return request_error(exception=ex) return request_error(exception=ex)
@api.route('/signin', methods=['POST']) @api_bp.route('/signin', methods=['POST'])
@internal_api_call @internal_api_call
def signin_user(): def signin_user():
signin_data = request.get_json() signin_data = request.get_json()
@ -318,7 +318,7 @@ def conduct_signin(username_or_email, password):
return response return response
@api.route("/signout", methods=['POST']) @api_bp.route("/signout", methods=['POST'])
@api_login_required @api_login_required
@internal_api_call @internal_api_call
def logout(): def logout():
@ -327,7 +327,7 @@ def logout():
return jsonify({'success': True}) return jsonify({'success': True})
@api.route("/recovery", methods=['POST']) @api_bp.route("/recovery", methods=['POST'])
@internal_api_call @internal_api_call
def request_recovery_email(): def request_recovery_email():
email = request.get_json()['email'] email = request.get_json()['email']
@ -336,7 +336,7 @@ def request_recovery_email():
return make_response('Created', 201) return make_response('Created', 201)
@api.route('/users/<prefix>', methods=['GET']) @api_bp.route('/users/<prefix>', methods=['GET'])
@api_login_required @api_login_required
def get_matching_users(prefix): def get_matching_users(prefix):
users = model.get_matching_users(prefix) users = model.get_matching_users(prefix)
@ -346,7 +346,7 @@ def get_matching_users(prefix):
}) })
@api.route('/entities/<prefix>', methods=['GET']) @api_bp.route('/entities/<prefix>', methods=['GET'])
@api_login_required @api_login_required
def get_matching_entities(prefix): def get_matching_entities(prefix):
teams = [] teams = []
@ -413,7 +413,7 @@ def team_view(orgname, team):
} }
@api.route('/organization/', methods=['POST']) @api_bp.route('/organization/', methods=['POST'])
@api_login_required @api_login_required
@internal_api_call @internal_api_call
def create_organization(): def create_organization():
@ -460,7 +460,7 @@ def org_view(o, teams):
return view return view
@api.route('/organization/<orgname>', methods=['GET']) @api_bp.route('/organization/<orgname>', methods=['GET'])
@api_login_required @api_login_required
def get_organization(orgname): def get_organization(orgname):
permission = OrganizationMemberPermission(orgname) permission = OrganizationMemberPermission(orgname)
@ -476,7 +476,7 @@ def get_organization(orgname):
abort(403) abort(403)
@api.route('/organization/<orgname>', methods=['PUT']) @api_bp.route('/organization/<orgname>', methods=['PUT'])
@api_login_required @api_login_required
@org_api_call('change_user_details') @org_api_call('change_user_details')
def change_organization_details(orgname): def change_organization_details(orgname):
@ -529,7 +529,7 @@ def prototype_view(proto, org_members):
'id': proto.uuid, 'id': proto.uuid,
} }
@api.route('/organization/<orgname>/prototypes', methods=['GET']) @api_bp.route('/organization/<orgname>/prototypes', methods=['GET'])
@api_login_required @api_login_required
def get_organization_prototype_permissions(orgname): def get_organization_prototype_permissions(orgname):
permission = AdministerOrganizationPermission(orgname) permission = AdministerOrganizationPermission(orgname)
@ -567,7 +567,7 @@ def log_prototype_action(action_kind, orgname, prototype, **kwargs):
log_action(action_kind, orgname, log_params) log_action(action_kind, orgname, log_params)
@api.route('/organization/<orgname>/prototypes', methods=['POST']) @api_bp.route('/organization/<orgname>/prototypes', methods=['POST'])
@api_login_required @api_login_required
def create_organization_prototype_permission(orgname): def create_organization_prototype_permission(orgname):
permission = AdministerOrganizationPermission(orgname) permission = AdministerOrganizationPermission(orgname)
@ -615,7 +615,7 @@ def create_organization_prototype_permission(orgname):
abort(403) abort(403)
@api.route('/organization/<orgname>/prototypes/<prototypeid>', @api_bp.route('/organization/<orgname>/prototypes/<prototypeid>',
methods=['DELETE']) methods=['DELETE'])
@api_login_required @api_login_required
def delete_organization_prototype_permission(orgname, prototypeid): def delete_organization_prototype_permission(orgname, prototypeid):
@ -637,7 +637,7 @@ def delete_organization_prototype_permission(orgname, prototypeid):
abort(403) abort(403)
@api.route('/organization/<orgname>/prototypes/<prototypeid>', @api_bp.route('/organization/<orgname>/prototypes/<prototypeid>',
methods=['PUT']) methods=['PUT'])
@api_login_required @api_login_required
def update_organization_prototype_permission(orgname, prototypeid): def update_organization_prototype_permission(orgname, prototypeid):
@ -666,7 +666,7 @@ def update_organization_prototype_permission(orgname, prototypeid):
abort(403) abort(403)
@api.route('/organization/<orgname>/members', methods=['GET']) @api_bp.route('/organization/<orgname>/members', methods=['GET'])
@api_login_required @api_login_required
def get_organization_members(orgname): def get_organization_members(orgname):
permission = AdministerOrganizationPermission(orgname) permission = AdministerOrganizationPermission(orgname)
@ -695,7 +695,7 @@ def get_organization_members(orgname):
abort(403) abort(403)
@api.route('/organization/<orgname>/members/<membername>', methods=['GET']) @api_bp.route('/organization/<orgname>/members/<membername>', methods=['GET'])
@api_login_required @api_login_required
def get_organization_member(orgname, membername): def get_organization_member(orgname, membername):
permission = AdministerOrganizationPermission(orgname) permission = AdministerOrganizationPermission(orgname)
@ -724,7 +724,7 @@ def get_organization_member(orgname, membername):
abort(403) abort(403)
@api.route('/organization/<orgname>/private', methods=['GET']) @api_bp.route('/organization/<orgname>/private', methods=['GET'])
@api_login_required @api_login_required
@internal_api_call @internal_api_call
@org_api_call('get_user_private_allowed') @org_api_call('get_user_private_allowed')
@ -764,7 +764,7 @@ def member_view(member):
} }
@api.route('/organization/<orgname>/team/<teamname>', @api_bp.route('/organization/<orgname>/team/<teamname>',
methods=['PUT', 'POST']) methods=['PUT', 'POST'])
@api_login_required @api_login_required
def update_organization_team(orgname, teamname): def update_organization_team(orgname, teamname):
@ -810,7 +810,7 @@ def update_organization_team(orgname, teamname):
abort(403) abort(403)
@api.route('/organization/<orgname>/team/<teamname>', @api_bp.route('/organization/<orgname>/team/<teamname>',
methods=['DELETE']) methods=['DELETE'])
@api_login_required @api_login_required
def delete_organization_team(orgname, teamname): def delete_organization_team(orgname, teamname):
@ -823,7 +823,7 @@ def delete_organization_team(orgname, teamname):
abort(403) abort(403)
@api.route('/organization/<orgname>/team/<teamname>/members', @api_bp.route('/organization/<orgname>/team/<teamname>/members',
methods=['GET']) methods=['GET'])
@api_login_required @api_login_required
def get_organization_team_members(orgname, teamname): def get_organization_team_members(orgname, teamname):
@ -846,7 +846,7 @@ def get_organization_team_members(orgname, teamname):
abort(403) abort(403)
@api.route('/organization/<orgname>/team/<teamname>/members/<membername>', @api_bp.route('/organization/<orgname>/team/<teamname>/members/<membername>',
methods=['PUT', 'POST']) methods=['PUT', 'POST'])
@api_login_required @api_login_required
def update_organization_team_member(orgname, teamname, membername): def update_organization_team_member(orgname, teamname, membername):
@ -875,7 +875,7 @@ def update_organization_team_member(orgname, teamname, membername):
abort(403) abort(403)
@api.route('/organization/<orgname>/team/<teamname>/members/<membername>', @api_bp.route('/organization/<orgname>/team/<teamname>/members/<membername>',
methods=['DELETE']) methods=['DELETE'])
@api_login_required @api_login_required
def delete_organization_team_member(orgname, teamname, membername): def delete_organization_team_member(orgname, teamname, membername):
@ -891,7 +891,7 @@ def delete_organization_team_member(orgname, teamname, membername):
abort(403) abort(403)
@api.route('/repository', methods=['POST']) @api_bp.route('/repository', methods=['POST'])
@api_login_required @api_login_required
def create_repo(): def create_repo():
owner = current_user.db_user() owner = current_user.db_user()
@ -925,7 +925,7 @@ def create_repo():
abort(403) abort(403)
@api.route('/find/repository', methods=['GET']) @api_bp.route('/find/repository', methods=['GET'])
def find_repos(): def find_repos():
prefix = request.args.get('query', '') prefix = request.args.get('query', '')
@ -948,7 +948,7 @@ def find_repos():
return jsonify(response) return jsonify(response)
@api.route('/repository/', methods=['GET']) @api_bp.route('/repository/', methods=['GET'])
def list_repos(): def list_repos():
def repo_view(repo_obj): def repo_view(repo_obj):
return { return {
@ -1003,7 +1003,7 @@ def list_repos():
return jsonify(response) return jsonify(response)
@api.route('/repository/<path:repository>', methods=['PUT']) @api_bp.route('/repository/<path:repository>', methods=['PUT'])
@api_login_required @api_login_required
@parse_repository_name @parse_repository_name
def update_repo(namespace, repository): def update_repo(namespace, repository):
@ -1025,7 +1025,7 @@ def update_repo(namespace, repository):
abort(403) abort(403)
@api.route('/repository/<path:repository>/changevisibility', @api_bp.route('/repository/<path:repository>/changevisibility',
methods=['POST']) methods=['POST'])
@api_login_required @api_login_required
@parse_repository_name @parse_repository_name
@ -1046,7 +1046,7 @@ def change_repo_visibility(namespace, repository):
abort(403) abort(403)
@api.route('/repository/<path:repository>', methods=['DELETE']) @api_bp.route('/repository/<path:repository>', methods=['DELETE'])
@api_login_required @api_login_required
@parse_repository_name @parse_repository_name
def delete_repository(namespace, repository): def delete_repository(namespace, repository):
@ -1077,7 +1077,7 @@ def image_view(image):
} }
@api.route('/repository/<path:repository>', methods=['GET']) @api_bp.route('/repository/<path:repository>', methods=['GET'])
@parse_repository_name @parse_repository_name
def get_repo(namespace, repository): def get_repo(namespace, repository):
logger.debug('Get repo: %s/%s' % (namespace, repository)) logger.debug('Get repo: %s/%s' % (namespace, repository))
@ -1159,7 +1159,7 @@ def build_status_view(build_obj, can_write=False):
} }
@api.route('/repository/<path:repository>/build/', methods=['GET']) @api_bp.route('/repository/<path:repository>/build/', methods=['GET'])
@parse_repository_name @parse_repository_name
def get_repo_builds(namespace, repository): def get_repo_builds(namespace, repository):
permission = ReadRepositoryPermission(namespace, repository) permission = ReadRepositoryPermission(namespace, repository)
@ -1176,7 +1176,7 @@ def get_repo_builds(namespace, repository):
abort(403) # Permission denied abort(403) # Permission denied
@api.route('/repository/<path:repository>/build/<build_uuid>/status', @api_bp.route('/repository/<path:repository>/build/<build_uuid>/status',
methods=['GET']) methods=['GET'])
@parse_repository_name @parse_repository_name
def get_repo_build_status(namespace, repository, build_uuid): def get_repo_build_status(namespace, repository, build_uuid):
@ -1193,7 +1193,7 @@ def get_repo_build_status(namespace, repository, build_uuid):
abort(403) # Permission denied abort(403) # Permission denied
@api.route('/repository/<path:repository>/build/<build_uuid>/archiveurl', @api_bp.route('/repository/<path:repository>/build/<build_uuid>/archiveurl',
methods=['GET']) methods=['GET'])
@parse_repository_name @parse_repository_name
def get_repo_build_archive_url(namespace, repository, build_uuid): def get_repo_build_archive_url(namespace, repository, build_uuid):
@ -1211,7 +1211,7 @@ def get_repo_build_archive_url(namespace, repository, build_uuid):
abort(403) # Permission denied abort(403) # Permission denied
@api.route('/repository/<path:repository>/build/<build_uuid>/logs', @api_bp.route('/repository/<path:repository>/build/<build_uuid>/logs',
methods=['GET']) methods=['GET'])
@parse_repository_name @parse_repository_name
def get_repo_build_logs(namespace, repository, build_uuid): def get_repo_build_logs(namespace, repository, build_uuid):
@ -1236,7 +1236,7 @@ def get_repo_build_logs(namespace, repository, build_uuid):
abort(403) # Permission denied abort(403) # Permission denied
@api.route('/repository/<path:repository>/build/', methods=['POST']) @api_bp.route('/repository/<path:repository>/build/', methods=['POST'])
@api_login_required @api_login_required
@parse_repository_name @parse_repository_name
def request_repo_build(namespace, repository): def request_repo_build(namespace, repository):
@ -1266,7 +1266,7 @@ def request_repo_build(namespace, repository):
resp = jsonify(build_status_view(build_request, True)) resp = jsonify(build_status_view(build_request, True))
repo_string = '%s/%s' % (namespace, repository) repo_string = '%s/%s' % (namespace, repository)
resp.headers['Location'] = url_for('api.get_repo_build_status', resp.headers['Location'] = url_for('api_bp.get_repo_build_status',
repository=repo_string, repository=repo_string,
build_uuid=build_request.uuid) build_uuid=build_request.uuid)
resp.status_code = 201 resp.status_code = 201
@ -1282,7 +1282,7 @@ def webhook_view(webhook):
} }
@api.route('/repository/<path:repository>/webhook/', methods=['POST']) @api_bp.route('/repository/<path:repository>/webhook/', methods=['POST'])
@api_login_required @api_login_required
@parse_repository_name @parse_repository_name
def create_webhook(namespace, repository): def create_webhook(namespace, repository):
@ -1292,7 +1292,7 @@ def create_webhook(namespace, repository):
webhook = model.create_webhook(repo, request.get_json()) webhook = model.create_webhook(repo, request.get_json())
resp = jsonify(webhook_view(webhook)) resp = jsonify(webhook_view(webhook))
repo_string = '%s/%s' % (namespace, repository) repo_string = '%s/%s' % (namespace, repository)
resp.headers['Location'] = url_for('api.get_webhook', resp.headers['Location'] = url_for('api_bp.get_webhook',
repository=repo_string, repository=repo_string,
public_id=webhook.public_id) public_id=webhook.public_id)
log_action('add_repo_webhook', namespace, log_action('add_repo_webhook', namespace,
@ -1303,7 +1303,7 @@ def create_webhook(namespace, repository):
abort(403) # Permissions denied abort(403) # Permissions denied
@api.route('/repository/<path:repository>/webhook/<public_id>', @api_bp.route('/repository/<path:repository>/webhook/<public_id>',
methods=['GET']) methods=['GET'])
@api_login_required @api_login_required
@parse_repository_name @parse_repository_name
@ -1320,7 +1320,7 @@ def get_webhook(namespace, repository, public_id):
abort(403) # Permission denied abort(403) # Permission denied
@api.route('/repository/<path:repository>/webhook/', methods=['GET']) @api_bp.route('/repository/<path:repository>/webhook/', methods=['GET'])
@api_login_required @api_login_required
@parse_repository_name @parse_repository_name
def list_webhooks(namespace, repository): def list_webhooks(namespace, repository):
@ -1334,7 +1334,7 @@ def list_webhooks(namespace, repository):
abort(403) # Permission denied abort(403) # Permission denied
@api.route('/repository/<path:repository>/webhook/<public_id>', @api_bp.route('/repository/<path:repository>/webhook/<public_id>',
methods=['DELETE']) methods=['DELETE'])
@api_login_required @api_login_required
@parse_repository_name @parse_repository_name
@ -1350,7 +1350,7 @@ def delete_webhook(namespace, repository, public_id):
abort(403) # Permission denied abort(403) # Permission denied
@api.route('/repository/<path:repository>/trigger/<trigger_uuid>', @api_bp.route('/repository/<path:repository>/trigger/<trigger_uuid>',
methods=['GET']) methods=['GET'])
@api_login_required @api_login_required
@parse_repository_name @parse_repository_name
@ -1372,7 +1372,7 @@ def _prepare_webhook_url(scheme, username, password, hostname, path):
return urlparse.urlunparse((scheme, auth_hostname, path, '', '', '')) return urlparse.urlunparse((scheme, auth_hostname, path, '', '', ''))
@api.route('/repository/<path:repository>/trigger/<trigger_uuid>/subdir', @api_bp.route('/repository/<path:repository>/trigger/<trigger_uuid>/subdir',
methods=['POST']) methods=['POST'])
@api_login_required @api_login_required
@parse_repository_name @parse_repository_name
@ -1405,7 +1405,7 @@ def list_build_trigger_subdirs(namespace, repository, trigger_uuid):
abort(403) # Permission denied abort(403) # Permission denied
@api.route('/repository/<path:repository>/trigger/<trigger_uuid>/activate', @api_bp.route('/repository/<path:repository>/trigger/<trigger_uuid>/activate',
methods=['POST']) methods=['POST'])
@api_login_required @api_login_required
@parse_repository_name @parse_repository_name
@ -1464,7 +1464,7 @@ def activate_build_trigger(namespace, repository, trigger_uuid):
abort(403) # Permission denied abort(403) # Permission denied
@api.route('/repository/<path:repository>/trigger/<trigger_uuid>/start', @api_bp.route('/repository/<path:repository>/trigger/<trigger_uuid>/start',
methods=['POST']) methods=['POST'])
@api_login_required @api_login_required
@parse_repository_name @parse_repository_name
@ -1493,7 +1493,7 @@ def manually_start_build_trigger(namespace, repository, trigger_uuid):
resp = jsonify(build_status_view(build_request, True)) resp = jsonify(build_status_view(build_request, True))
repo_string = '%s/%s' % (namespace, repository) repo_string = '%s/%s' % (namespace, repository)
resp.headers['Location'] = url_for('api.get_repo_build_status', resp.headers['Location'] = url_for('api_bp.get_repo_build_status',
repository=repo_string, repository=repo_string,
build_uuid=build_request.uuid) build_uuid=build_request.uuid)
resp.status_code = 201 resp.status_code = 201
@ -1502,7 +1502,7 @@ def manually_start_build_trigger(namespace, repository, trigger_uuid):
abort(403) # Permission denied abort(403) # Permission denied
@api.route('/repository/<path:repository>/trigger/<trigger_uuid>/builds', @api_bp.route('/repository/<path:repository>/trigger/<trigger_uuid>/builds',
methods=['GET']) methods=['GET'])
@api_login_required @api_login_required
@parse_repository_name @parse_repository_name
@ -1519,7 +1519,7 @@ def list_trigger_recent_builds(namespace, repository, trigger_uuid):
abort(403) # Permission denied abort(403) # Permission denied
@api.route('/repository/<path:repository>/trigger/<trigger_uuid>/sources', @api_bp.route('/repository/<path:repository>/trigger/<trigger_uuid>/sources',
methods=['GET']) methods=['GET'])
@api_login_required @api_login_required
@parse_repository_name @parse_repository_name
@ -1543,7 +1543,7 @@ def list_trigger_build_sources(namespace, repository, trigger_uuid):
@api.route('/repository/<path:repository>/trigger/', methods=['GET']) @api_bp.route('/repository/<path:repository>/trigger/', methods=['GET'])
@api_login_required @api_login_required
@parse_repository_name @parse_repository_name
def list_build_triggers(namespace, repository): def list_build_triggers(namespace, repository):
@ -1557,7 +1557,7 @@ def list_build_triggers(namespace, repository):
abort(403) # Permission denied abort(403) # Permission denied
@api.route('/repository/<path:repository>/trigger/<trigger_uuid>', @api_bp.route('/repository/<path:repository>/trigger/<trigger_uuid>',
methods=['DELETE']) methods=['DELETE'])
@api_login_required @api_login_required
@parse_repository_name @parse_repository_name
@ -1590,7 +1590,7 @@ def delete_build_trigger(namespace, repository, trigger_uuid):
abort(403) # Permission denied abort(403) # Permission denied
@api.route('/filedrop/', methods=['POST']) @api_bp.route('/filedrop/', methods=['POST'])
@api_login_required @api_login_required
@internal_api_call @internal_api_call
def get_filedrop_url(): def get_filedrop_url():
@ -1617,7 +1617,7 @@ def wrap_role_view_org(role_json, user, org_members):
return role_json return role_json
@api.route('/repository/<path:repository>/image/', methods=['GET']) @api_bp.route('/repository/<path:repository>/image/', methods=['GET'])
@parse_repository_name @parse_repository_name
def list_repository_images(namespace, repository): def list_repository_images(namespace, repository):
permission = ReadRepositoryPermission(namespace, repository) permission = ReadRepositoryPermission(namespace, repository)
@ -1642,7 +1642,7 @@ def list_repository_images(namespace, repository):
abort(403) abort(403)
@api.route('/repository/<path:repository>/image/<image_id>', @api_bp.route('/repository/<path:repository>/image/<image_id>',
methods=['GET']) methods=['GET'])
@parse_repository_name @parse_repository_name
def get_image(namespace, repository, image_id): def get_image(namespace, repository, image_id):
@ -1656,7 +1656,7 @@ def get_image(namespace, repository, image_id):
abort(403) abort(403)
@api.route('/repository/<path:repository>/image/<image_id>/changes', @api_bp.route('/repository/<path:repository>/image/<image_id>/changes',
methods=['GET']) methods=['GET'])
@cache_control(max_age=60*60) # Cache for one hour @cache_control(max_age=60*60) # Cache for one hour
@parse_repository_name @parse_repository_name
@ -1681,7 +1681,7 @@ def get_image_changes(namespace, repository, image_id):
abort(403) abort(403)
@api.route('/repository/<path:repository>/tag/<tag>', @api_bp.route('/repository/<path:repository>/tag/<tag>',
methods=['DELETE']) methods=['DELETE'])
@parse_repository_name @parse_repository_name
def delete_full_tag(namespace, repository, tag): def delete_full_tag(namespace, repository, tag):
@ -1700,7 +1700,7 @@ def delete_full_tag(namespace, repository, tag):
abort(403) # Permission denied abort(403) # Permission denied
@api.route('/repository/<path:repository>/tag/<tag>/images', @api_bp.route('/repository/<path:repository>/tag/<tag>/images',
methods=['GET']) methods=['GET'])
@parse_repository_name @parse_repository_name
def list_tag_images(namespace, repository, tag): def list_tag_images(namespace, repository, tag):
@ -1724,7 +1724,7 @@ def list_tag_images(namespace, repository, tag):
abort(403) # Permission denied abort(403) # Permission denied
@api.route('/repository/<path:repository>/permissions/team/', @api_bp.route('/repository/<path:repository>/permissions/team/',
methods=['GET']) methods=['GET'])
@api_login_required @api_login_required
@parse_repository_name @parse_repository_name
@ -1741,7 +1741,7 @@ def list_repo_team_permissions(namespace, repository):
abort(403) # Permission denied abort(403) # Permission denied
@api.route('/repository/<path:repository>/permissions/user/', @api_bp.route('/repository/<path:repository>/permissions/user/',
methods=['GET']) methods=['GET'])
@api_login_required @api_login_required
@parse_repository_name @parse_repository_name
@ -1782,7 +1782,7 @@ def list_repo_user_permissions(namespace, repository):
abort(403) # Permission denied abort(403) # Permission denied
@api.route('/repository/<path:repository>/permissions/user/<username>', @api_bp.route('/repository/<path:repository>/permissions/user/<username>',
methods=['GET']) methods=['GET'])
@api_login_required @api_login_required
@parse_repository_name @parse_repository_name
@ -1807,7 +1807,7 @@ def get_user_permissions(namespace, repository, username):
abort(403) # Permission denied abort(403) # Permission denied
@api.route('/repository/<path:repository>/permissions/team/<teamname>', @api_bp.route('/repository/<path:repository>/permissions/team/<teamname>',
methods=['GET']) methods=['GET'])
@api_login_required @api_login_required
@parse_repository_name @parse_repository_name
@ -1822,7 +1822,7 @@ def get_team_permissions(namespace, repository, teamname):
abort(403) # Permission denied abort(403) # Permission denied
@api.route('/repository/<path:repository>/permissions/user/<username>', @api_bp.route('/repository/<path:repository>/permissions/user/<username>',
methods=['PUT', 'POST']) methods=['PUT', 'POST'])
@api_login_required @api_login_required
@parse_repository_name @parse_repository_name
@ -1861,7 +1861,7 @@ def change_user_permissions(namespace, repository, username):
abort(403) # Permission denied abort(403) # Permission denied
@api.route('/repository/<path:repository>/permissions/team/<teamname>', @api_bp.route('/repository/<path:repository>/permissions/team/<teamname>',
methods=['PUT', 'POST']) methods=['PUT', 'POST'])
@api_login_required @api_login_required
@parse_repository_name @parse_repository_name
@ -1889,7 +1889,7 @@ def change_team_permissions(namespace, repository, teamname):
abort(403) # Permission denied abort(403) # Permission denied
@api.route('/repository/<path:repository>/permissions/user/<username>', @api_bp.route('/repository/<path:repository>/permissions/user/<username>',
methods=['DELETE']) methods=['DELETE'])
@api_login_required @api_login_required
@parse_repository_name @parse_repository_name
@ -1910,7 +1910,7 @@ def delete_user_permissions(namespace, repository, username):
abort(403) # Permission denied abort(403) # Permission denied
@api.route('/repository/<path:repository>/permissions/team/<teamname>', @api_bp.route('/repository/<path:repository>/permissions/team/<teamname>',
methods=['DELETE']) methods=['DELETE'])
@api_login_required @api_login_required
@parse_repository_name @parse_repository_name
@ -1936,7 +1936,7 @@ def token_view(token_obj):
} }
@api.route('/repository/<path:repository>/tokens/', methods=['GET']) @api_bp.route('/repository/<path:repository>/tokens/', methods=['GET'])
@api_login_required @api_login_required
@parse_repository_name @parse_repository_name
def list_repo_tokens(namespace, repository): def list_repo_tokens(namespace, repository):
@ -1951,7 +1951,7 @@ def list_repo_tokens(namespace, repository):
abort(403) # Permission denied abort(403) # Permission denied
@api.route('/repository/<path:repository>/tokens/<code>', methods=['GET']) @api_bp.route('/repository/<path:repository>/tokens/<code>', methods=['GET'])
@api_login_required @api_login_required
@parse_repository_name @parse_repository_name
def get_tokens(namespace, repository, code): def get_tokens(namespace, repository, code):
@ -1967,7 +1967,7 @@ def get_tokens(namespace, repository, code):
abort(403) # Permission denied abort(403) # Permission denied
@api.route('/repository/<path:repository>/tokens/', methods=['POST']) @api_bp.route('/repository/<path:repository>/tokens/', methods=['POST'])
@api_login_required @api_login_required
@parse_repository_name @parse_repository_name
def create_token(namespace, repository): def create_token(namespace, repository):
@ -1989,7 +1989,7 @@ def create_token(namespace, repository):
abort(403) # Permission denied abort(403) # Permission denied
@api.route('/repository/<path:repository>/tokens/<code>', methods=['PUT']) @api_bp.route('/repository/<path:repository>/tokens/<code>', methods=['PUT'])
@api_login_required @api_login_required
@parse_repository_name @parse_repository_name
def change_token(namespace, repository, code): def change_token(namespace, repository, code):
@ -2014,7 +2014,7 @@ def change_token(namespace, repository, code):
abort(403) # Permission denied abort(403) # Permission denied
@api.route('/repository/<path:repository>/tokens/<code>', @api_bp.route('/repository/<path:repository>/tokens/<code>',
methods=['DELETE']) methods=['DELETE'])
@api_login_required @api_login_required
@parse_repository_name @parse_repository_name
@ -2042,7 +2042,7 @@ def subscription_view(stripe_subscription, used_repos):
} }
@api.route('/user/card', methods=['GET']) @api_bp.route('/user/card', methods=['GET'])
@api_login_required @api_login_required
@internal_api_call @internal_api_call
def get_user_card(): def get_user_card():
@ -2050,7 +2050,7 @@ def get_user_card():
return get_card(user) return get_card(user)
@api.route('/organization/<orgname>/card', methods=['GET']) @api_bp.route('/organization/<orgname>/card', methods=['GET'])
@api_login_required @api_login_required
@internal_api_call @internal_api_call
@org_api_call('get_user_card') @org_api_call('get_user_card')
@ -2063,7 +2063,7 @@ def get_org_card(orgname):
abort(403) abort(403)
@api.route('/user/card', methods=['POST']) @api_bp.route('/user/card', methods=['POST'])
@api_login_required @api_login_required
@internal_api_call @internal_api_call
def set_user_card(): def set_user_card():
@ -2074,7 +2074,7 @@ def set_user_card():
return response return response
@api.route('/organization/<orgname>/card', methods=['POST']) @api_bp.route('/organization/<orgname>/card', methods=['POST'])
@api_login_required @api_login_required
@org_api_call('set_user_card') @org_api_call('set_user_card')
def set_org_card(orgname): def set_org_card(orgname):
@ -2128,7 +2128,7 @@ def get_card(user):
return jsonify({'card': card_info}) return jsonify({'card': card_info})
@api.route('/user/plan', methods=['PUT']) @api_bp.route('/user/plan', methods=['PUT'])
@api_login_required @api_login_required
@internal_api_call @internal_api_call
def update_user_subscription(): def update_user_subscription():
@ -2221,7 +2221,7 @@ def subscribe(user, plan, token, require_business_plan):
return resp return resp
@api.route('/user/invoices', methods=['GET']) @api_bp.route('/user/invoices', methods=['GET'])
@api_login_required @api_login_required
def list_user_invoices(): def list_user_invoices():
user = current_user.db_user() user = current_user.db_user()
@ -2231,7 +2231,7 @@ def list_user_invoices():
return get_invoices(user.stripe_id) return get_invoices(user.stripe_id)
@api.route('/organization/<orgname>/invoices', methods=['GET']) @api_bp.route('/organization/<orgname>/invoices', methods=['GET'])
@api_login_required @api_login_required
@org_api_call('list_user_invoices') @org_api_call('list_user_invoices')
def list_org_invoices(orgname): def list_org_invoices(orgname):
@ -2268,7 +2268,7 @@ def get_invoices(customer_id):
}) })
@api.route('/organization/<orgname>/plan', methods=['PUT']) @api_bp.route('/organization/<orgname>/plan', methods=['PUT'])
@api_login_required @api_login_required
@internal_api_call @internal_api_call
@org_api_call('update_user_subscription') @org_api_call('update_user_subscription')
@ -2284,7 +2284,7 @@ def update_org_subscription(orgname):
abort(403) abort(403)
@api.route('/user/plan', methods=['GET']) @api_bp.route('/user/plan', methods=['GET'])
@api_login_required @api_login_required
@internal_api_call @internal_api_call
def get_user_subscription(): def get_user_subscription():
@ -2303,7 +2303,7 @@ def get_user_subscription():
}) })
@api.route('/organization/<orgname>/plan', methods=['GET']) @api_bp.route('/organization/<orgname>/plan', methods=['GET'])
@api_login_required @api_login_required
@internal_api_call @internal_api_call
@org_api_call('get_user_subscription') @org_api_call('get_user_subscription')
@ -2333,7 +2333,7 @@ def robot_view(name, token):
} }
@api.route('/user/robots', methods=['GET']) @api_bp.route('/user/robots', methods=['GET'])
@api_login_required @api_login_required
def get_user_robots(): def get_user_robots():
user = current_user.db_user() user = current_user.db_user()
@ -2343,7 +2343,7 @@ def get_user_robots():
}) })
@api.route('/organization/<orgname>/robots', methods=['GET']) @api_bp.route('/organization/<orgname>/robots', methods=['GET'])
@api_login_required @api_login_required
@org_api_call('get_user_robots') @org_api_call('get_user_robots')
def get_org_robots(orgname): def get_org_robots(orgname):
@ -2357,7 +2357,7 @@ def get_org_robots(orgname):
abort(403) abort(403)
@api.route('/user/robots/<robot_shortname>', methods=['PUT']) @api_bp.route('/user/robots/<robot_shortname>', methods=['PUT'])
@api_login_required @api_login_required
def create_user_robot(robot_shortname): def create_user_robot(robot_shortname):
parent = current_user.db_user() parent = current_user.db_user()
@ -2368,7 +2368,7 @@ def create_user_robot(robot_shortname):
return resp return resp
@api.route('/organization/<orgname>/robots/<robot_shortname>', @api_bp.route('/organization/<orgname>/robots/<robot_shortname>',
methods=['PUT']) methods=['PUT'])
@api_login_required @api_login_required
@org_api_call('create_user_robot') @org_api_call('create_user_robot')
@ -2385,7 +2385,7 @@ def create_org_robot(orgname, robot_shortname):
abort(403) abort(403)
@api.route('/user/robots/<robot_shortname>', methods=['DELETE']) @api_bp.route('/user/robots/<robot_shortname>', methods=['DELETE'])
@api_login_required @api_login_required
def delete_user_robot(robot_shortname): def delete_user_robot(robot_shortname):
parent = current_user.db_user() parent = current_user.db_user()
@ -2394,7 +2394,7 @@ def delete_user_robot(robot_shortname):
return make_response('Deleted', 204) return make_response('Deleted', 204)
@api.route('/organization/<orgname>/robots/<robot_shortname>', @api_bp.route('/organization/<orgname>/robots/<robot_shortname>',
methods=['DELETE']) methods=['DELETE'])
@api_login_required @api_login_required
@org_api_call('delete_user_robot') @org_api_call('delete_user_robot')
@ -2427,7 +2427,7 @@ def log_view(log):
@api.route('/repository/<path:repository>/logs', methods=['GET']) @api_bp.route('/repository/<path:repository>/logs', methods=['GET'])
@api_login_required @api_login_required
@parse_repository_name @parse_repository_name
def list_repo_logs(namespace, repository): def list_repo_logs(namespace, repository):
@ -2444,7 +2444,7 @@ def list_repo_logs(namespace, repository):
abort(403) abort(403)
@api.route('/organization/<orgname>/logs', methods=['GET']) @api_bp.route('/organization/<orgname>/logs', methods=['GET'])
@api_login_required @api_login_required
@org_api_call('list_user_logs') @org_api_call('list_user_logs')
def list_org_logs(orgname): def list_org_logs(orgname):
@ -2460,7 +2460,7 @@ def list_org_logs(orgname):
abort(403) abort(403)
@api.route('/user/logs', methods=['GET']) @api_bp.route('/user/logs', methods=['GET'])
@api_login_required @api_login_required
def list_user_logs(): def list_user_logs():
performer_name = request.args.get('performer', None) performer_name = request.args.get('performer', None)

View file

@ -1,13 +1,14 @@
import logging import logging
import json import json
from flask.ext.restful import Resource, Api, reqparse, abort from flask.ext.restful import Resource, reqparse, abort
from flask.ext.login import current_user from flask.ext.login import current_user
from data import model from data import model
from endpoints.api import (api, truthy_bool, format_date, nickname, log_action, from endpoints.api import (truthy_bool, format_date, nickname, log_action,
validate_json_request, require_repo_read, validate_json_request, require_repo_read,
RepositoryParamResource) RepositoryParamResource, resource, query_parameter,
parse_args)
from auth.permissions import (ReadRepositoryPermission, from auth.permissions import (ReadRepositoryPermission,
ModifyRepositoryPermission, ModifyRepositoryPermission,
AdministerRepositoryPermission) AdministerRepositoryPermission)
@ -15,15 +16,6 @@ from auth.permissions import (ReadRepositoryPermission,
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
repo_api = Api(api)
def resource(*urls, **kwargs):
def wrapper(api_resource):
repo_api.add_resource(api_resource, *urls, **kwargs)
return api_resource
return wrapper
@resource('/v1/repository') @resource('/v1/repository')
class RepositoryList(Resource): class RepositoryList(Resource):
@ -97,17 +89,21 @@ class RepositoryList(Resource):
abort(403) abort(403)
@nickname('listRepos') @nickname('listRepos')
def get(self): @parse_args
parser = reqparse.RequestParser() @query_parameter('page', 'Offset page number. (int)', type=int)
parser.add_argument('page', type=int, help='Page number must be an int.') @query_parameter('limit', 'Limit on the number of results (int)', type=int)
parser.add_argument('limit', type=int, help='Limit must be an int.') @query_parameter('namespace', ('Namespace to use when querying for org '
parser.add_argument('namespace', type=str) 'repositories.'), type=str)
parser.add_argument('public', type=truthy_bool, default=True) @query_parameter('public', 'Whether to include public repositories.',
parser.add_argument('private', type=truthy_bool, default=True) type=truthy_bool, default=True)
parser.add_argument('sort', type=truthy_bool, default=False) @query_parameter('private', 'Whether to inlcude private repositories.',
parser.add_argument('count', type=truthy_bool, default=False) type=truthy_bool, default=True)
args = parser.parse_args() @query_parameter('sort', 'Whether to sort the results.', type=truthy_bool,
default=False)
@query_parameter('count', ('Whether to include a count of the total number '
'of results available.'), type=truthy_bool,
default=False)
def get(self, args):
def repo_view(repo_obj): def repo_view(repo_obj):
return { return {
'namespace': repo_obj.namespace, 'namespace': repo_obj.namespace,