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
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')

View file

@ -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):

View file

@ -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 '
'<a href="https://quay.io">Quay.io</a>.'),
'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')

View file

@ -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/<prefix>', methods=['GET'])
@api_bp.route('/users/<prefix>', 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/<prefix>', methods=['GET'])
@api_bp.route('/entities/<prefix>', 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/<orgname>', methods=['GET'])
@api_bp.route('/organization/<orgname>', 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/<orgname>', methods=['PUT'])
@api_bp.route('/organization/<orgname>', 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/<orgname>/prototypes', methods=['GET'])
@api_bp.route('/organization/<orgname>/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/<orgname>/prototypes', methods=['POST'])
@api_bp.route('/organization/<orgname>/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/<orgname>/prototypes/<prototypeid>',
@api_bp.route('/organization/<orgname>/prototypes/<prototypeid>',
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/<orgname>/prototypes/<prototypeid>',
@api_bp.route('/organization/<orgname>/prototypes/<prototypeid>',
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/<orgname>/members', methods=['GET'])
@api_bp.route('/organization/<orgname>/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/<orgname>/members/<membername>', methods=['GET'])
@api_bp.route('/organization/<orgname>/members/<membername>', 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/<orgname>/private', methods=['GET'])
@api_bp.route('/organization/<orgname>/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/<orgname>/team/<teamname>',
@api_bp.route('/organization/<orgname>/team/<teamname>',
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/<orgname>/team/<teamname>',
@api_bp.route('/organization/<orgname>/team/<teamname>',
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/<orgname>/team/<teamname>/members',
@api_bp.route('/organization/<orgname>/team/<teamname>/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/<orgname>/team/<teamname>/members/<membername>',
@api_bp.route('/organization/<orgname>/team/<teamname>/members/<membername>',
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/<orgname>/team/<teamname>/members/<membername>',
@api_bp.route('/organization/<orgname>/team/<teamname>/members/<membername>',
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/<path:repository>', methods=['PUT'])
@api_bp.route('/repository/<path: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/<path:repository>/changevisibility',
@api_bp.route('/repository/<path: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/<path:repository>', methods=['DELETE'])
@api_bp.route('/repository/<path: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/<path:repository>', methods=['GET'])
@api_bp.route('/repository/<path: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/<path:repository>/build/', methods=['GET'])
@api_bp.route('/repository/<path: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/<path:repository>/build/<build_uuid>/status',
@api_bp.route('/repository/<path:repository>/build/<build_uuid>/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/<path:repository>/build/<build_uuid>/archiveurl',
@api_bp.route('/repository/<path:repository>/build/<build_uuid>/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/<path:repository>/build/<build_uuid>/logs',
@api_bp.route('/repository/<path:repository>/build/<build_uuid>/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/<path:repository>/build/', methods=['POST'])
@api_bp.route('/repository/<path: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/<path:repository>/webhook/', methods=['POST'])
@api_bp.route('/repository/<path: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/<path:repository>/webhook/<public_id>',
@api_bp.route('/repository/<path:repository>/webhook/<public_id>',
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/<path:repository>/webhook/', methods=['GET'])
@api_bp.route('/repository/<path: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/<path:repository>/webhook/<public_id>',
@api_bp.route('/repository/<path:repository>/webhook/<public_id>',
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/<path:repository>/trigger/<trigger_uuid>',
@api_bp.route('/repository/<path:repository>/trigger/<trigger_uuid>',
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/<path:repository>/trigger/<trigger_uuid>/subdir',
@api_bp.route('/repository/<path:repository>/trigger/<trigger_uuid>/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/<path:repository>/trigger/<trigger_uuid>/activate',
@api_bp.route('/repository/<path:repository>/trigger/<trigger_uuid>/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/<path:repository>/trigger/<trigger_uuid>/start',
@api_bp.route('/repository/<path:repository>/trigger/<trigger_uuid>/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/<path:repository>/trigger/<trigger_uuid>/builds',
@api_bp.route('/repository/<path:repository>/trigger/<trigger_uuid>/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/<path:repository>/trigger/<trigger_uuid>/sources',
@api_bp.route('/repository/<path:repository>/trigger/<trigger_uuid>/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/<path:repository>/trigger/', methods=['GET'])
@api_bp.route('/repository/<path: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/<path:repository>/trigger/<trigger_uuid>',
@api_bp.route('/repository/<path:repository>/trigger/<trigger_uuid>',
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/<path:repository>/image/', methods=['GET'])
@api_bp.route('/repository/<path: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/<path:repository>/image/<image_id>',
@api_bp.route('/repository/<path:repository>/image/<image_id>',
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/<path:repository>/image/<image_id>/changes',
@api_bp.route('/repository/<path:repository>/image/<image_id>/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/<path:repository>/tag/<tag>',
@api_bp.route('/repository/<path:repository>/tag/<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/<path:repository>/tag/<tag>/images',
@api_bp.route('/repository/<path:repository>/tag/<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/<path:repository>/permissions/team/',
@api_bp.route('/repository/<path: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/<path:repository>/permissions/user/',
@api_bp.route('/repository/<path: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/<path:repository>/permissions/user/<username>',
@api_bp.route('/repository/<path:repository>/permissions/user/<username>',
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/<path:repository>/permissions/team/<teamname>',
@api_bp.route('/repository/<path:repository>/permissions/team/<teamname>',
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/<path:repository>/permissions/user/<username>',
@api_bp.route('/repository/<path:repository>/permissions/user/<username>',
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/<path:repository>/permissions/team/<teamname>',
@api_bp.route('/repository/<path:repository>/permissions/team/<teamname>',
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/<path:repository>/permissions/user/<username>',
@api_bp.route('/repository/<path:repository>/permissions/user/<username>',
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/<path:repository>/permissions/team/<teamname>',
@api_bp.route('/repository/<path:repository>/permissions/team/<teamname>',
methods=['DELETE'])
@api_login_required
@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
@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/<path:repository>/tokens/<code>', methods=['GET'])
@api_bp.route('/repository/<path:repository>/tokens/<code>', 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/<path:repository>/tokens/', methods=['POST'])
@api_bp.route('/repository/<path: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/<path:repository>/tokens/<code>', methods=['PUT'])
@api_bp.route('/repository/<path:repository>/tokens/<code>', 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/<path:repository>/tokens/<code>',
@api_bp.route('/repository/<path:repository>/tokens/<code>',
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/<orgname>/card', methods=['GET'])
@api_bp.route('/organization/<orgname>/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/<orgname>/card', methods=['POST'])
@api_bp.route('/organization/<orgname>/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/<orgname>/invoices', methods=['GET'])
@api_bp.route('/organization/<orgname>/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/<orgname>/plan', methods=['PUT'])
@api_bp.route('/organization/<orgname>/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/<orgname>/plan', methods=['GET'])
@api_bp.route('/organization/<orgname>/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/<orgname>/robots', methods=['GET'])
@api_bp.route('/organization/<orgname>/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/<robot_shortname>', methods=['PUT'])
@api_bp.route('/user/robots/<robot_shortname>', 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/<orgname>/robots/<robot_shortname>',
@api_bp.route('/organization/<orgname>/robots/<robot_shortname>',
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/<robot_shortname>', methods=['DELETE'])
@api_bp.route('/user/robots/<robot_shortname>', 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/<orgname>/robots/<robot_shortname>',
@api_bp.route('/organization/<orgname>/robots/<robot_shortname>',
methods=['DELETE'])
@api_login_required
@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
@parse_repository_name
def list_repo_logs(namespace, repository):
@ -2444,7 +2444,7 @@ def list_repo_logs(namespace, repository):
abort(403)
@api.route('/organization/<orgname>/logs', methods=['GET'])
@api_bp.route('/organization/<orgname>/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)

View file

@ -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,