diff --git a/application.py b/application.py index 3d962e6ce..91062d4f6 100644 --- a/application.py +++ b/application.py @@ -9,7 +9,7 @@ application.config['LOGGING_CONFIG']() # Turn off debug logging for boto logging.getLogger('boto').setLevel(logging.CRITICAL) -from endpoints.api import api +from endpoints.api import api_bp from endpoints.index import index from endpoints.web import web 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(tags, 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(realtime, url_prefix='/realtime') diff --git a/endpoints/api/__init__.py b/endpoints/api/__init__.py index 2116018a1..16e6dcdb9 100644 --- a/endpoints/api/__init__.py +++ b/endpoints/api/__init__.py @@ -1,5 +1,8 @@ +import logging + 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 calendar import timegm from email.utils import formatdate @@ -13,8 +16,17 @@ from auth.permissions import (ReadRepositoryPermission, 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): @@ -44,6 +56,41 @@ def method_metadata(func, name): 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): @wraps(func) def wrapper(repository, *args, **kwargs): diff --git a/endpoints/api/discovery.py b/endpoints/api/discovery.py index a1bf03980..081dc05c0 100644 --- a/endpoints/api/discovery.py +++ b/endpoints/api/discovery.py @@ -1,17 +1,26 @@ import re +import logging -from flask.ext.restful import Api, Resource -from flask.ext.restful.utils.cors import crossdomain +from flask.ext.restful import Resource, reqparse -from endpoints.api import api, method_metadata, nickname -from endpoints.common import get_route_data +from endpoints.api import resource, method_metadata, nickname, truthy_bool from app import app -discovery_api = Api(api) -discovery_api.decorators = [crossdomain(origin='*')] +logger = logging.getLogger(__name__) + + +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(): apis = [] @@ -49,6 +58,22 @@ def swagger_route_data(): schema = endpoint_method.view_class.schemas[req_schema_name] 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: operations.append({ 'method': method_name, @@ -57,7 +82,7 @@ def swagger_route_data(): 'parameters': parameters, }) - swagger_path = param_regex.sub(r'{\2}', rule.rule) + swagger_path = PARAM_REGEX.sub(r'{\2}', rule.rule) apis.append({ 'path': swagger_path, 'description': 'Resource description.', @@ -67,18 +92,24 @@ def swagger_route_data(): swagger_data = { 'apiVersion': 'v1', 'swaggerVersion': '1.2', - 'basePath': 'https://quay.io/', + 'basePath': 'http://ci.devtable.com:5000', '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 ' + 'Quay.io.'), + 'termsOfServiceUrl': 'https://quay.io/tos', + 'contact': 'support@quay.io', + }, 'apis': apis, 'models': models, } return swagger_data - +@resource('/v1/discovery') class DiscoveryResource(Resource): @nickname('discovery') def get(self): return swagger_route_data() - - -discovery_api.add_resource(DiscoveryResource, '/v1/discovery') \ No newline at end of file diff --git a/endpoints/api/legacy.py b/endpoints/api/legacy.py index b776330d5..b03de7006 100644 --- a/endpoints/api/legacy.py +++ b/endpoints/api/legacy.py @@ -11,7 +11,7 @@ from functools import wraps from collections import defaultdict from urllib import quote -from endpoints.api import api +from endpoints.api import api_bp from data import model from data.plans import PLANS, get_plan from app import app @@ -43,7 +43,7 @@ build_logs = app.config['BUILDLOGS'] logger = logging.getLogger(__name__) -@api.before_request +@api_bp.before_request def csrf_protect(): if request.method != "GET" and request.method != "HEAD": token = session.get('_csrf_token', None) @@ -111,18 +111,18 @@ def org_api_call(user_call_name): return internal_decorator -@api.route('/discovery') +@api_bp.route('/discovery') def discovery(): return jsonify(get_route_data()) -@api.route('/') +@api_bp.route('/') @internal_api_call def welcome(): return jsonify({'version': '0.5'}) -@api.route('/plans/') +@api_bp.route('/plans/') def list_plans(): return jsonify({ 'plans': PLANS, @@ -165,7 +165,7 @@ def user_view(user): } -@api.route('/user/', methods=['GET']) +@api_bp.route('/user/', methods=['GET']) @internal_api_call def get_logged_in_user(): if current_user.is_anonymous(): @@ -178,7 +178,7 @@ def get_logged_in_user(): return jsonify(user_view(user)) -@api.route('/user/private', methods=['GET']) +@api_bp.route('/user/private', methods=['GET']) @api_login_required @internal_api_call 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 @internal_api_call def convert_user_to_organization(): @@ -230,7 +230,7 @@ def convert_user_to_organization(): return conduct_signin(admin_username, admin_password) -@api.route('/user/', methods=['PUT']) +@api_bp.route('/user/', methods=['PUT']) @api_login_required @internal_api_call def change_user_details(): @@ -264,7 +264,7 @@ def change_user_details(): return jsonify(user_view(user)) -@api.route('/user/', methods=['POST']) +@api_bp.route('/user/', methods=['POST']) @internal_api_call def create_new_user(): user_data = request.get_json() @@ -283,7 +283,7 @@ def create_new_user(): return request_error(exception=ex) -@api.route('/signin', methods=['POST']) +@api_bp.route('/signin', methods=['POST']) @internal_api_call def signin_user(): signin_data = request.get_json() @@ -318,7 +318,7 @@ def conduct_signin(username_or_email, password): return response -@api.route("/signout", methods=['POST']) +@api_bp.route("/signout", methods=['POST']) @api_login_required @internal_api_call def logout(): @@ -327,7 +327,7 @@ def logout(): return jsonify({'success': True}) -@api.route("/recovery", methods=['POST']) +@api_bp.route("/recovery", methods=['POST']) @internal_api_call def request_recovery_email(): email = request.get_json()['email'] @@ -336,7 +336,7 @@ def request_recovery_email(): return make_response('Created', 201) -@api.route('/users/', methods=['GET']) +@api_bp.route('/users/', methods=['GET']) @api_login_required def get_matching_users(prefix): users = model.get_matching_users(prefix) @@ -346,7 +346,7 @@ def get_matching_users(prefix): }) -@api.route('/entities/', methods=['GET']) +@api_bp.route('/entities/', methods=['GET']) @api_login_required def get_matching_entities(prefix): teams = [] @@ -413,7 +413,7 @@ def team_view(orgname, team): } -@api.route('/organization/', methods=['POST']) +@api_bp.route('/organization/', methods=['POST']) @api_login_required @internal_api_call def create_organization(): @@ -460,7 +460,7 @@ def org_view(o, teams): return view -@api.route('/organization/', methods=['GET']) +@api_bp.route('/organization/', methods=['GET']) @api_login_required def get_organization(orgname): permission = OrganizationMemberPermission(orgname) @@ -476,7 +476,7 @@ def get_organization(orgname): abort(403) -@api.route('/organization/', methods=['PUT']) +@api_bp.route('/organization/', methods=['PUT']) @api_login_required @org_api_call('change_user_details') def change_organization_details(orgname): @@ -529,7 +529,7 @@ def prototype_view(proto, org_members): 'id': proto.uuid, } -@api.route('/organization//prototypes', methods=['GET']) +@api_bp.route('/organization//prototypes', methods=['GET']) @api_login_required def get_organization_prototype_permissions(orgname): permission = AdministerOrganizationPermission(orgname) @@ -567,7 +567,7 @@ def log_prototype_action(action_kind, orgname, prototype, **kwargs): log_action(action_kind, orgname, log_params) -@api.route('/organization//prototypes', methods=['POST']) +@api_bp.route('/organization//prototypes', methods=['POST']) @api_login_required def create_organization_prototype_permission(orgname): permission = AdministerOrganizationPermission(orgname) @@ -615,7 +615,7 @@ def create_organization_prototype_permission(orgname): abort(403) -@api.route('/organization//prototypes/', +@api_bp.route('/organization//prototypes/', methods=['DELETE']) @api_login_required def delete_organization_prototype_permission(orgname, prototypeid): @@ -637,7 +637,7 @@ def delete_organization_prototype_permission(orgname, prototypeid): abort(403) -@api.route('/organization//prototypes/', +@api_bp.route('/organization//prototypes/', methods=['PUT']) @api_login_required def update_organization_prototype_permission(orgname, prototypeid): @@ -666,7 +666,7 @@ def update_organization_prototype_permission(orgname, prototypeid): abort(403) -@api.route('/organization//members', methods=['GET']) +@api_bp.route('/organization//members', methods=['GET']) @api_login_required def get_organization_members(orgname): permission = AdministerOrganizationPermission(orgname) @@ -695,7 +695,7 @@ def get_organization_members(orgname): abort(403) -@api.route('/organization//members/', methods=['GET']) +@api_bp.route('/organization//members/', methods=['GET']) @api_login_required def get_organization_member(orgname, membername): permission = AdministerOrganizationPermission(orgname) @@ -724,7 +724,7 @@ def get_organization_member(orgname, membername): abort(403) -@api.route('/organization//private', methods=['GET']) +@api_bp.route('/organization//private', methods=['GET']) @api_login_required @internal_api_call @org_api_call('get_user_private_allowed') @@ -764,7 +764,7 @@ def member_view(member): } -@api.route('/organization//team/', +@api_bp.route('/organization//team/', methods=['PUT', 'POST']) @api_login_required def update_organization_team(orgname, teamname): @@ -810,7 +810,7 @@ def update_organization_team(orgname, teamname): abort(403) -@api.route('/organization//team/', +@api_bp.route('/organization//team/', methods=['DELETE']) @api_login_required def delete_organization_team(orgname, teamname): @@ -823,7 +823,7 @@ def delete_organization_team(orgname, teamname): abort(403) -@api.route('/organization//team//members', +@api_bp.route('/organization//team//members', methods=['GET']) @api_login_required def get_organization_team_members(orgname, teamname): @@ -846,7 +846,7 @@ def get_organization_team_members(orgname, teamname): abort(403) -@api.route('/organization//team//members/', +@api_bp.route('/organization//team//members/', methods=['PUT', 'POST']) @api_login_required def update_organization_team_member(orgname, teamname, membername): @@ -875,7 +875,7 @@ def update_organization_team_member(orgname, teamname, membername): abort(403) -@api.route('/organization//team//members/', +@api_bp.route('/organization//team//members/', methods=['DELETE']) @api_login_required def delete_organization_team_member(orgname, teamname, membername): @@ -891,7 +891,7 @@ def delete_organization_team_member(orgname, teamname, membername): abort(403) -@api.route('/repository', methods=['POST']) +@api_bp.route('/repository', methods=['POST']) @api_login_required def create_repo(): owner = current_user.db_user() @@ -925,7 +925,7 @@ def create_repo(): abort(403) -@api.route('/find/repository', methods=['GET']) +@api_bp.route('/find/repository', methods=['GET']) def find_repos(): prefix = request.args.get('query', '') @@ -948,7 +948,7 @@ def find_repos(): return jsonify(response) -@api.route('/repository/', methods=['GET']) +@api_bp.route('/repository/', methods=['GET']) def list_repos(): def repo_view(repo_obj): return { @@ -1003,7 +1003,7 @@ def list_repos(): return jsonify(response) -@api.route('/repository/', methods=['PUT']) +@api_bp.route('/repository/', methods=['PUT']) @api_login_required @parse_repository_name def update_repo(namespace, repository): @@ -1025,7 +1025,7 @@ def update_repo(namespace, repository): abort(403) -@api.route('/repository//changevisibility', +@api_bp.route('/repository//changevisibility', methods=['POST']) @api_login_required @parse_repository_name @@ -1046,7 +1046,7 @@ def change_repo_visibility(namespace, repository): abort(403) -@api.route('/repository/', methods=['DELETE']) +@api_bp.route('/repository/', methods=['DELETE']) @api_login_required @parse_repository_name def delete_repository(namespace, repository): @@ -1077,7 +1077,7 @@ def image_view(image): } -@api.route('/repository/', methods=['GET']) +@api_bp.route('/repository/', methods=['GET']) @parse_repository_name def get_repo(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//build/', methods=['GET']) +@api_bp.route('/repository//build/', methods=['GET']) @parse_repository_name def get_repo_builds(namespace, repository): permission = ReadRepositoryPermission(namespace, repository) @@ -1176,7 +1176,7 @@ def get_repo_builds(namespace, repository): abort(403) # Permission denied -@api.route('/repository//build//status', +@api_bp.route('/repository//build//status', methods=['GET']) @parse_repository_name 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 -@api.route('/repository//build//archiveurl', +@api_bp.route('/repository//build//archiveurl', methods=['GET']) @parse_repository_name 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 -@api.route('/repository//build//logs', +@api_bp.route('/repository//build//logs', methods=['GET']) @parse_repository_name 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 -@api.route('/repository//build/', methods=['POST']) +@api_bp.route('/repository//build/', methods=['POST']) @api_login_required @parse_repository_name def request_repo_build(namespace, repository): @@ -1266,7 +1266,7 @@ def request_repo_build(namespace, repository): resp = jsonify(build_status_view(build_request, True)) 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, build_uuid=build_request.uuid) resp.status_code = 201 @@ -1282,7 +1282,7 @@ def webhook_view(webhook): } -@api.route('/repository//webhook/', methods=['POST']) +@api_bp.route('/repository//webhook/', methods=['POST']) @api_login_required @parse_repository_name def create_webhook(namespace, repository): @@ -1292,7 +1292,7 @@ def create_webhook(namespace, repository): webhook = model.create_webhook(repo, request.get_json()) resp = jsonify(webhook_view(webhook)) 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, public_id=webhook.public_id) log_action('add_repo_webhook', namespace, @@ -1303,7 +1303,7 @@ def create_webhook(namespace, repository): abort(403) # Permissions denied -@api.route('/repository//webhook/', +@api_bp.route('/repository//webhook/', methods=['GET']) @api_login_required @parse_repository_name @@ -1320,7 +1320,7 @@ def get_webhook(namespace, repository, public_id): abort(403) # Permission denied -@api.route('/repository//webhook/', methods=['GET']) +@api_bp.route('/repository//webhook/', methods=['GET']) @api_login_required @parse_repository_name def list_webhooks(namespace, repository): @@ -1334,7 +1334,7 @@ def list_webhooks(namespace, repository): abort(403) # Permission denied -@api.route('/repository//webhook/', +@api_bp.route('/repository//webhook/', methods=['DELETE']) @api_login_required @parse_repository_name @@ -1350,7 +1350,7 @@ def delete_webhook(namespace, repository, public_id): abort(403) # Permission denied -@api.route('/repository//trigger/', +@api_bp.route('/repository//trigger/', methods=['GET']) @api_login_required @parse_repository_name @@ -1372,7 +1372,7 @@ def _prepare_webhook_url(scheme, username, password, hostname, path): return urlparse.urlunparse((scheme, auth_hostname, path, '', '', '')) -@api.route('/repository//trigger//subdir', +@api_bp.route('/repository//trigger//subdir', methods=['POST']) @api_login_required @parse_repository_name @@ -1405,7 +1405,7 @@ def list_build_trigger_subdirs(namespace, repository, trigger_uuid): abort(403) # Permission denied -@api.route('/repository//trigger//activate', +@api_bp.route('/repository//trigger//activate', methods=['POST']) @api_login_required @parse_repository_name @@ -1464,7 +1464,7 @@ def activate_build_trigger(namespace, repository, trigger_uuid): abort(403) # Permission denied -@api.route('/repository//trigger//start', +@api_bp.route('/repository//trigger//start', methods=['POST']) @api_login_required @parse_repository_name @@ -1493,7 +1493,7 @@ def manually_start_build_trigger(namespace, repository, trigger_uuid): resp = jsonify(build_status_view(build_request, True)) 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, build_uuid=build_request.uuid) resp.status_code = 201 @@ -1502,7 +1502,7 @@ def manually_start_build_trigger(namespace, repository, trigger_uuid): abort(403) # Permission denied -@api.route('/repository//trigger//builds', +@api_bp.route('/repository//trigger//builds', methods=['GET']) @api_login_required @parse_repository_name @@ -1519,7 +1519,7 @@ def list_trigger_recent_builds(namespace, repository, trigger_uuid): abort(403) # Permission denied -@api.route('/repository//trigger//sources', +@api_bp.route('/repository//trigger//sources', methods=['GET']) @api_login_required @parse_repository_name @@ -1543,7 +1543,7 @@ def list_trigger_build_sources(namespace, repository, trigger_uuid): -@api.route('/repository//trigger/', methods=['GET']) +@api_bp.route('/repository//trigger/', methods=['GET']) @api_login_required @parse_repository_name def list_build_triggers(namespace, repository): @@ -1557,7 +1557,7 @@ def list_build_triggers(namespace, repository): abort(403) # Permission denied -@api.route('/repository//trigger/', +@api_bp.route('/repository//trigger/', methods=['DELETE']) @api_login_required @parse_repository_name @@ -1590,7 +1590,7 @@ def delete_build_trigger(namespace, repository, trigger_uuid): abort(403) # Permission denied -@api.route('/filedrop/', methods=['POST']) +@api_bp.route('/filedrop/', methods=['POST']) @api_login_required @internal_api_call def get_filedrop_url(): @@ -1617,7 +1617,7 @@ def wrap_role_view_org(role_json, user, org_members): return role_json -@api.route('/repository//image/', methods=['GET']) +@api_bp.route('/repository//image/', methods=['GET']) @parse_repository_name def list_repository_images(namespace, repository): permission = ReadRepositoryPermission(namespace, repository) @@ -1642,7 +1642,7 @@ def list_repository_images(namespace, repository): abort(403) -@api.route('/repository//image/', +@api_bp.route('/repository//image/', methods=['GET']) @parse_repository_name def get_image(namespace, repository, image_id): @@ -1656,7 +1656,7 @@ def get_image(namespace, repository, image_id): abort(403) -@api.route('/repository//image//changes', +@api_bp.route('/repository//image//changes', methods=['GET']) @cache_control(max_age=60*60) # Cache for one hour @parse_repository_name @@ -1681,7 +1681,7 @@ def get_image_changes(namespace, repository, image_id): abort(403) -@api.route('/repository//tag/', +@api_bp.route('/repository//tag/', methods=['DELETE']) @parse_repository_name def delete_full_tag(namespace, repository, tag): @@ -1700,7 +1700,7 @@ def delete_full_tag(namespace, repository, tag): abort(403) # Permission denied -@api.route('/repository//tag//images', +@api_bp.route('/repository//tag//images', methods=['GET']) @parse_repository_name def list_tag_images(namespace, repository, tag): @@ -1724,7 +1724,7 @@ def list_tag_images(namespace, repository, tag): abort(403) # Permission denied -@api.route('/repository//permissions/team/', +@api_bp.route('/repository//permissions/team/', methods=['GET']) @api_login_required @parse_repository_name @@ -1741,7 +1741,7 @@ def list_repo_team_permissions(namespace, repository): abort(403) # Permission denied -@api.route('/repository//permissions/user/', +@api_bp.route('/repository//permissions/user/', methods=['GET']) @api_login_required @parse_repository_name @@ -1782,7 +1782,7 @@ def list_repo_user_permissions(namespace, repository): abort(403) # Permission denied -@api.route('/repository//permissions/user/', +@api_bp.route('/repository//permissions/user/', methods=['GET']) @api_login_required @parse_repository_name @@ -1807,7 +1807,7 @@ def get_user_permissions(namespace, repository, username): abort(403) # Permission denied -@api.route('/repository//permissions/team/', +@api_bp.route('/repository//permissions/team/', methods=['GET']) @api_login_required @parse_repository_name @@ -1822,7 +1822,7 @@ def get_team_permissions(namespace, repository, teamname): abort(403) # Permission denied -@api.route('/repository//permissions/user/', +@api_bp.route('/repository//permissions/user/', methods=['PUT', 'POST']) @api_login_required @parse_repository_name @@ -1861,7 +1861,7 @@ def change_user_permissions(namespace, repository, username): abort(403) # Permission denied -@api.route('/repository//permissions/team/', +@api_bp.route('/repository//permissions/team/', methods=['PUT', 'POST']) @api_login_required @parse_repository_name @@ -1889,7 +1889,7 @@ def change_team_permissions(namespace, repository, teamname): abort(403) # Permission denied -@api.route('/repository//permissions/user/', +@api_bp.route('/repository//permissions/user/', methods=['DELETE']) @api_login_required @parse_repository_name @@ -1910,7 +1910,7 @@ def delete_user_permissions(namespace, repository, username): abort(403) # Permission denied -@api.route('/repository//permissions/team/', +@api_bp.route('/repository//permissions/team/', methods=['DELETE']) @api_login_required @parse_repository_name @@ -1936,7 +1936,7 @@ def token_view(token_obj): } -@api.route('/repository//tokens/', methods=['GET']) +@api_bp.route('/repository//tokens/', methods=['GET']) @api_login_required @parse_repository_name def list_repo_tokens(namespace, repository): @@ -1951,7 +1951,7 @@ def list_repo_tokens(namespace, repository): abort(403) # Permission denied -@api.route('/repository//tokens/', methods=['GET']) +@api_bp.route('/repository//tokens/', methods=['GET']) @api_login_required @parse_repository_name def get_tokens(namespace, repository, code): @@ -1967,7 +1967,7 @@ def get_tokens(namespace, repository, code): abort(403) # Permission denied -@api.route('/repository//tokens/', methods=['POST']) +@api_bp.route('/repository//tokens/', methods=['POST']) @api_login_required @parse_repository_name def create_token(namespace, repository): @@ -1989,7 +1989,7 @@ def create_token(namespace, repository): abort(403) # Permission denied -@api.route('/repository//tokens/', methods=['PUT']) +@api_bp.route('/repository//tokens/', methods=['PUT']) @api_login_required @parse_repository_name def change_token(namespace, repository, code): @@ -2014,7 +2014,7 @@ def change_token(namespace, repository, code): abort(403) # Permission denied -@api.route('/repository//tokens/', +@api_bp.route('/repository//tokens/', methods=['DELETE']) @api_login_required @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 @internal_api_call def get_user_card(): @@ -2050,7 +2050,7 @@ def get_user_card(): return get_card(user) -@api.route('/organization//card', methods=['GET']) +@api_bp.route('/organization//card', methods=['GET']) @api_login_required @internal_api_call @org_api_call('get_user_card') @@ -2063,7 +2063,7 @@ def get_org_card(orgname): abort(403) -@api.route('/user/card', methods=['POST']) +@api_bp.route('/user/card', methods=['POST']) @api_login_required @internal_api_call def set_user_card(): @@ -2074,7 +2074,7 @@ def set_user_card(): return response -@api.route('/organization//card', methods=['POST']) +@api_bp.route('/organization//card', methods=['POST']) @api_login_required @org_api_call('set_user_card') def set_org_card(orgname): @@ -2128,7 +2128,7 @@ def get_card(user): return jsonify({'card': card_info}) -@api.route('/user/plan', methods=['PUT']) +@api_bp.route('/user/plan', methods=['PUT']) @api_login_required @internal_api_call def update_user_subscription(): @@ -2221,7 +2221,7 @@ def subscribe(user, plan, token, require_business_plan): return resp -@api.route('/user/invoices', methods=['GET']) +@api_bp.route('/user/invoices', methods=['GET']) @api_login_required def list_user_invoices(): user = current_user.db_user() @@ -2231,7 +2231,7 @@ def list_user_invoices(): return get_invoices(user.stripe_id) -@api.route('/organization//invoices', methods=['GET']) +@api_bp.route('/organization//invoices', methods=['GET']) @api_login_required @org_api_call('list_user_invoices') def list_org_invoices(orgname): @@ -2268,7 +2268,7 @@ def get_invoices(customer_id): }) -@api.route('/organization//plan', methods=['PUT']) +@api_bp.route('/organization//plan', methods=['PUT']) @api_login_required @internal_api_call @org_api_call('update_user_subscription') @@ -2284,7 +2284,7 @@ def update_org_subscription(orgname): abort(403) -@api.route('/user/plan', methods=['GET']) +@api_bp.route('/user/plan', methods=['GET']) @api_login_required @internal_api_call def get_user_subscription(): @@ -2303,7 +2303,7 @@ def get_user_subscription(): }) -@api.route('/organization//plan', methods=['GET']) +@api_bp.route('/organization//plan', methods=['GET']) @api_login_required @internal_api_call @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 def get_user_robots(): user = current_user.db_user() @@ -2343,7 +2343,7 @@ def get_user_robots(): }) -@api.route('/organization//robots', methods=['GET']) +@api_bp.route('/organization//robots', methods=['GET']) @api_login_required @org_api_call('get_user_robots') def get_org_robots(orgname): @@ -2357,7 +2357,7 @@ def get_org_robots(orgname): abort(403) -@api.route('/user/robots/', methods=['PUT']) +@api_bp.route('/user/robots/', methods=['PUT']) @api_login_required def create_user_robot(robot_shortname): parent = current_user.db_user() @@ -2368,7 +2368,7 @@ def create_user_robot(robot_shortname): return resp -@api.route('/organization//robots/', +@api_bp.route('/organization//robots/', methods=['PUT']) @api_login_required @org_api_call('create_user_robot') @@ -2385,7 +2385,7 @@ def create_org_robot(orgname, robot_shortname): abort(403) -@api.route('/user/robots/', methods=['DELETE']) +@api_bp.route('/user/robots/', methods=['DELETE']) @api_login_required def delete_user_robot(robot_shortname): parent = current_user.db_user() @@ -2394,7 +2394,7 @@ def delete_user_robot(robot_shortname): return make_response('Deleted', 204) -@api.route('/organization//robots/', +@api_bp.route('/organization//robots/', methods=['DELETE']) @api_login_required @org_api_call('delete_user_robot') @@ -2427,7 +2427,7 @@ def log_view(log): -@api.route('/repository//logs', methods=['GET']) +@api_bp.route('/repository//logs', methods=['GET']) @api_login_required @parse_repository_name def list_repo_logs(namespace, repository): @@ -2444,7 +2444,7 @@ def list_repo_logs(namespace, repository): abort(403) -@api.route('/organization//logs', methods=['GET']) +@api_bp.route('/organization//logs', methods=['GET']) @api_login_required @org_api_call('list_user_logs') def list_org_logs(orgname): @@ -2460,7 +2460,7 @@ def list_org_logs(orgname): abort(403) -@api.route('/user/logs', methods=['GET']) +@api_bp.route('/user/logs', methods=['GET']) @api_login_required def list_user_logs(): performer_name = request.args.get('performer', None) diff --git a/endpoints/api/repository.py b/endpoints/api/repository.py index 1f9dae710..8bda3a115 100644 --- a/endpoints/api/repository.py +++ b/endpoints/api/repository.py @@ -1,13 +1,14 @@ import logging 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 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, - RepositoryParamResource) + RepositoryParamResource, resource, query_parameter, + parse_args) from auth.permissions import (ReadRepositoryPermission, ModifyRepositoryPermission, AdministerRepositoryPermission) @@ -15,15 +16,6 @@ from auth.permissions import (ReadRepositoryPermission, 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') class RepositoryList(Resource): @@ -97,17 +89,21 @@ class RepositoryList(Resource): abort(403) @nickname('listRepos') - def get(self): - parser = reqparse.RequestParser() - parser.add_argument('page', type=int, help='Page number must be an int.') - parser.add_argument('limit', type=int, help='Limit must be an int.') - parser.add_argument('namespace', type=str) - parser.add_argument('public', type=truthy_bool, default=True) - parser.add_argument('private', type=truthy_bool, default=True) - parser.add_argument('sort', type=truthy_bool, default=False) - parser.add_argument('count', type=truthy_bool, default=False) - args = parser.parse_args() - + @parse_args + @query_parameter('page', 'Offset page number. (int)', type=int) + @query_parameter('limit', 'Limit on the number of results (int)', type=int) + @query_parameter('namespace', ('Namespace to use when querying for org ' + 'repositories.'), type=str) + @query_parameter('public', 'Whether to include public repositories.', + type=truthy_bool, default=True) + @query_parameter('private', 'Whether to inlcude private repositories.', + type=truthy_bool, default=True) + @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): return { 'namespace': repo_obj.namespace,