More fully replicate the swagger API.
This commit is contained in:
parent
de1a44f853
commit
b3e0dfae48
3 changed files with 157 additions and 43 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
Reference in a new issue