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 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__)
@ -19,21 +29,74 @@ def format_date(date):
def add_method_metadata(name, value):
def modifier(func):
if '__api_metadata' not in dir(func):
func.__metadata = {}
func.__metadata[name] = value
func.__api_metadata = {}
func.__api_metadata[name] = value
return func
return modifier
def method_metadata(func, name):
if '__api_metadata' in dir(func):
return func.__metadata.get(name, None)
return func.__api_metadata.get(name, None)
return None
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.repository

View file

@ -1,6 +1,7 @@
import re
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.common import get_route_data
@ -8,11 +9,13 @@ from app import app
discovery_api = Api(api)
discovery_api.decorators = [crossdomain(origin='*')]
param_regex = re.compile(r'<([\w]+:)?([\w]+)>')
def swagger_route_data():
apis = []
models = {}
for rule in app.url_map.iter_rules():
endpoint_method = app.view_functions[rule.endpoint]
@ -30,9 +33,22 @@ def swagger_route_data():
'name': param,
'dataType': 'string',
'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:
operations.append({
'method': method_name,
@ -52,7 +68,9 @@ def swagger_route_data():
'apiVersion': 'v1',
'swaggerVersion': '1.2',
'basePath': 'https://quay.io/',
'resourcePath': '/',
'apis': apis,
'models': models,
}
return swagger_data

View file

@ -1,34 +1,23 @@
import logging
import json
from functools import wraps
from flask.ext.restful import Resource, Api, reqparse, abort, fields
from flask.ext.restful import Resource, Api, reqparse, abort
from flask.ext.login import current_user
from data import model
from endpoints.api import api, truthy_bool, format_date, nickname
from util.names import parse_namespace_repository
from endpoints.api import (api, truthy_bool, format_date, nickname, log_action,
validate_json_request, require_repo_read,
RepositoryParamResource)
from auth.permissions import (ReadRepositoryPermission,
ModifyRepositoryPermission,
AdministerRepositoryPermission)
logger = logging.getLogger(__name__)
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 wrapper(api_resource):
repo_api.add_resource(api_resource, *urls, **kwargs)
@ -36,32 +25,76 @@ def resource(*urls, **kwargs):
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')
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')
@validate_json_request('NewRepo')
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')
def get(self):