More fully replicate the swagger API.

This commit is contained in:
jakedt 2014-03-10 23:54:55 -04:00
parent de1a44f853
commit b3e0dfae48
3 changed files with 157 additions and 43 deletions

View file

@ -1,7 +1,17 @@
from flask import Blueprint from flask import Blueprint, request
from flask.ext.restful import Resource, abort
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
from functools import partial from functools import partial, wraps
from jsonschema import validate, ValidationError
from data import model
from util.names import parse_namespace_repository
from auth.permissions import (ReadRepositoryPermission,
ModifyRepositoryPermission,
AdministerRepositoryPermission)
api = Blueprint('api', __name__) api = Blueprint('api', __name__)
@ -19,21 +29,74 @@ def format_date(date):
def add_method_metadata(name, value): def add_method_metadata(name, value):
def modifier(func): def modifier(func):
if '__api_metadata' not in dir(func): if '__api_metadata' not in dir(func):
func.__metadata = {} func.__api_metadata = {}
func.__metadata[name] = value func.__api_metadata[name] = value
return func return func
return modifier return modifier
def method_metadata(func, name): def method_metadata(func, name):
if '__api_metadata' in dir(func): if '__api_metadata' in dir(func):
return func.__metadata.get(name, None) return func.__api_metadata.get(name, None)
return None return None
nickname = partial(add_method_metadata, 'nickname') nickname = partial(add_method_metadata, 'nickname')
def parse_repository_name(func):
@wraps(func)
def wrapper(repository, *args, **kwargs):
(namespace, repository) = parse_namespace_repository(repository)
return func(namespace, repository, *args, **kwargs)
return wrapper
class RepositoryParamResource(Resource):
method_decorators = [parse_repository_name]
def require_repo_permission(permission_class, allow_public=False):
def wrapper(func):
@wraps(func)
def wrapped(self, namespace, repository, *args, **kwargs):
permission = permission_class(namespace, repository)
if (permission.can() or
(allow_public and
model.repository_is_public(namespace, repository))):
return func(self, namespace, repository, *args, **kwargs)
abort(403)
return wrapped
return wrapper
require_repo_read = require_repo_permission(ReadRepositoryPermission, True)
require_repo_write = require_repo_permission(ModifyRepositoryPermission)
require_repo_admin = require_repo_permission(AdministerRepositoryPermission)
def validate_json_request(schema_name):
def wrapper(func):
@add_method_metadata('request_schema', schema_name)
@wraps(func)
def wrapped(self, namespace, repository, *args, **kwargs):
schema = self.schemas[schema_name]
try:
validate(request.get_json(), schema)
return func(self, namespace, repository, *args, **kwargs)
except ValidationError as ex:
abort(400, message=ex.message)
return wrapped
return wrapper
def log_action(kind, user_or_orgname, metadata={}, repo=None):
performer = current_user.db_user()
model.log_action(kind, user_or_orgname, performer=performer,
ip=request.remote_addr, metadata=metadata, repository=repo)
import endpoints.api.legacy import endpoints.api.legacy
import endpoints.api.repository import endpoints.api.repository

View file

@ -1,6 +1,7 @@
import re import re
from flask.ext.restful import Api, Resource from flask.ext.restful import Api, Resource
from flask.ext.restful.utils.cors import crossdomain
from endpoints.api import api, method_metadata, nickname from endpoints.api import api, method_metadata, nickname
from endpoints.common import get_route_data from endpoints.common import get_route_data
@ -8,11 +9,13 @@ from app import app
discovery_api = Api(api) discovery_api = Api(api)
discovery_api.decorators = [crossdomain(origin='*')]
param_regex = re.compile(r'<([\w]+:)?([\w]+)>') param_regex = re.compile(r'<([\w]+:)?([\w]+)>')
def swagger_route_data(): def swagger_route_data():
apis = [] apis = []
models = {}
for rule in app.url_map.iter_rules(): for rule in app.url_map.iter_rules():
endpoint_method = app.view_functions[rule.endpoint] endpoint_method = app.view_functions[rule.endpoint]
@ -30,9 +33,22 @@ def swagger_route_data():
'name': param, 'name': param,
'dataType': 'string', 'dataType': 'string',
'description': 'Param description.', 'description': 'Param description.',
'required': True 'required': True,
}) })
req_schema_name = method_metadata(method, 'request_schema')
if req_schema_name:
parameters.append({
'paramType': 'body',
'name': 'request_body',
'description': 'Request body contents.',
'dataType': req_schema_name,
'required': True,
})
schema = endpoint_method.view_class.schemas[req_schema_name]
models[req_schema_name] = schema
if method is not None: if method is not None:
operations.append({ operations.append({
'method': method_name, 'method': method_name,
@ -52,7 +68,9 @@ def swagger_route_data():
'apiVersion': 'v1', 'apiVersion': 'v1',
'swaggerVersion': '1.2', 'swaggerVersion': '1.2',
'basePath': 'https://quay.io/', 'basePath': 'https://quay.io/',
'resourcePath': '/',
'apis': apis, 'apis': apis,
'models': models,
} }
return swagger_data return swagger_data

View file

@ -1,34 +1,23 @@
import logging import logging
import json import json
from functools import wraps from flask.ext.restful import Resource, Api, reqparse, abort
from flask.ext.restful import Resource, Api, reqparse, abort, fields
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 from endpoints.api import (api, truthy_bool, format_date, nickname, log_action,
from util.names import parse_namespace_repository validate_json_request, require_repo_read,
RepositoryParamResource)
from auth.permissions import (ReadRepositoryPermission, from auth.permissions import (ReadRepositoryPermission,
ModifyRepositoryPermission, ModifyRepositoryPermission,
AdministerRepositoryPermission) AdministerRepositoryPermission)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
repo_api = Api(api) repo_api = Api(api)
def parse_repository_name(f):
@wraps(f)
def wrapper(repository, *args, **kwargs):
(namespace, repository) = parse_namespace_repository(repository)
return f(namespace, repository, *args, **kwargs)
return wrapper
class RepositoryParamResource(Resource):
method_decorators = [parse_repository_name]
def resource(*urls, **kwargs): def resource(*urls, **kwargs):
def wrapper(api_resource): def wrapper(api_resource):
repo_api.add_resource(api_resource, *urls, **kwargs) repo_api.add_resource(api_resource, *urls, **kwargs)
@ -36,32 +25,76 @@ def resource(*urls, **kwargs):
return wrapper return wrapper
def require_repo_permission(permission_class, allow_public=False):
def wrapper(func):
@wraps(func)
def wrapped(self, namespace, repository, *args, **kwargs):
permission = permission_class(namespace, repository)
if (permission.can() or
(allow_public and
model.repository_is_public(namespace, repository))):
return func(self, namespace, repository, *args, **kwargs)
abort(403)
func.__required_permission = 'read'
return wrapped
return wrapper
require_repo_read = require_repo_permission(ReadRepositoryPermission, True)
require_repo_write = require_repo_permission(ModifyRepositoryPermission)
require_repo_admin = require_repo_permission(AdministerRepositoryPermission)
@resource('/v1/repository') @resource('/v1/repository')
class RepositoryList(Resource): class RepositoryList(Resource):
schemas = {
'NewRepo': {
'id': 'NewRepo',
'type': 'object',
'description': 'Description of a new repository.',
'required': [
'repository',
'visibility',
],
'properties': {
'repository': {
'type': 'string',
'description': 'Repository name.',
},
'visibility': {
'type': 'string',
'description': 'Visibility which the repository will start with.',
'enum': [
'public',
'private',
]
},
'namespace': {
'type': 'string',
'description': ('Namespace in which the repository should be '
'created. If omitted, the username of the caller is'
'used.'),
},
'description': {
'type': 'string',
'description': 'Markdown encoded description for the repository.',
},
}
}
}
@nickname('createRepo') @nickname('createRepo')
@validate_json_request('NewRepo')
def post(self): def post(self):
pass owner = current_user.db_user()
req = request.get_json()
namespace_name = req['namespace'] if 'namespace' in req else owner.username
permission = CreateRepositoryPermission(namespace_name)
if permission.can():
repository_name = req['repository']
visibility = req['visibility']
existing = model.get_repository(namespace_name, repository_name)
if existing:
return request_error(message='Repository already exists')
visibility = req['visibility']
repo = model.create_repository(namespace_name, repository_name, owner,
visibility)
repo.description = req['description']
repo.save()
log_action('create_repo', namespace_name,
{'repo': repository_name, 'namespace': namespace_name},
repo=repo)
return jsonify({
'namespace': namespace_name,
'name': repository_name
})
abort(403)
@nickname('listRepos') @nickname('listRepos')
def get(self): def get(self):