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 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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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):
|
||||||
|
|
Reference in a new issue