2014-03-10 22:30:41 +00:00
|
|
|
import re
|
2014-03-11 16:57:33 +00:00
|
|
|
import logging
|
2014-03-10 22:30:41 +00:00
|
|
|
|
2014-03-12 20:31:37 +00:00
|
|
|
from flask.ext.restful import reqparse
|
2014-03-10 22:30:41 +00:00
|
|
|
|
2014-03-14 22:07:03 +00:00
|
|
|
from endpoints.api import (ApiResource, resource, method_metadata, nickname, truthy_bool,
|
|
|
|
parse_args, query_param)
|
2014-03-10 22:30:41 +00:00
|
|
|
from app import app
|
2014-03-12 16:37:06 +00:00
|
|
|
from auth import scopes
|
2014-03-10 22:30:41 +00:00
|
|
|
|
|
|
|
|
2014-03-11 16:57:33 +00:00
|
|
|
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',
|
|
|
|
}
|
2014-03-10 22:30:41 +00:00
|
|
|
|
|
|
|
|
2014-03-14 21:35:52 +00:00
|
|
|
def fully_qualified_name(method_view_class):
|
|
|
|
inst = method_view_class()
|
|
|
|
return '%s.%s' % (inst.__module__, inst.__class__.__name__)
|
|
|
|
|
|
|
|
|
2014-03-15 03:40:41 +00:00
|
|
|
def swagger_route_data(include_internal=False, compact=False):
|
2014-03-10 22:30:41 +00:00
|
|
|
apis = []
|
2014-03-11 03:54:55 +00:00
|
|
|
models = {}
|
2014-03-10 22:30:41 +00:00
|
|
|
for rule in app.url_map.iter_rules():
|
|
|
|
endpoint_method = app.view_functions[rule.endpoint]
|
|
|
|
|
|
|
|
if 'view_class' in dir(endpoint_method):
|
2014-03-11 19:20:03 +00:00
|
|
|
view_class = endpoint_method.view_class
|
2014-03-10 22:30:41 +00:00
|
|
|
operations = []
|
|
|
|
|
|
|
|
method_names = list(rule.methods.difference(['HEAD', 'OPTIONS']))
|
|
|
|
for method_name in method_names:
|
2014-03-11 19:20:03 +00:00
|
|
|
method = getattr(view_class, method_name.lower(), None)
|
2014-03-10 22:30:41 +00:00
|
|
|
|
|
|
|
parameters = []
|
|
|
|
for param in rule.arguments:
|
|
|
|
parameters.append({
|
|
|
|
'paramType': 'path',
|
|
|
|
'name': param,
|
|
|
|
'dataType': 'string',
|
|
|
|
'description': 'Param description.',
|
2014-03-11 03:54:55 +00:00
|
|
|
'required': True,
|
2014-03-10 22:30:41 +00:00
|
|
|
})
|
|
|
|
|
2014-03-14 21:35:52 +00:00
|
|
|
if method is None:
|
|
|
|
logger.debug('Unable to find method for %s in class %s', method_name, view_class)
|
|
|
|
else:
|
2014-03-12 16:37:06 +00:00
|
|
|
req_schema_name = method_metadata(method, 'request_schema')
|
|
|
|
if req_schema_name:
|
|
|
|
parameters.append({
|
|
|
|
'paramType': 'body',
|
|
|
|
'name': 'body',
|
|
|
|
'description': 'Request body contents.',
|
|
|
|
'dataType': req_schema_name,
|
|
|
|
'required': True,
|
|
|
|
})
|
|
|
|
|
|
|
|
schema = view_class.schemas[req_schema_name]
|
|
|
|
models[req_schema_name] = schema
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
new_operation = {
|
2014-03-10 22:30:41 +00:00
|
|
|
'method': method_name,
|
2014-03-15 03:40:41 +00:00
|
|
|
'nickname': method_metadata(method, 'nickname')
|
2014-03-12 16:37:06 +00:00
|
|
|
}
|
|
|
|
|
2014-03-15 03:40:41 +00:00
|
|
|
if not compact:
|
|
|
|
new_operation.update({
|
|
|
|
'type': 'void',
|
|
|
|
'summary': method.__doc__ if method.__doc__ else '',
|
|
|
|
'parameters': parameters,
|
|
|
|
})
|
|
|
|
|
|
|
|
|
2014-03-12 16:37:06 +00:00
|
|
|
scope = method_metadata(method, 'oauth2_scope')
|
2014-03-15 03:40:41 +00:00
|
|
|
if scope and not compact:
|
2014-03-12 16:37:06 +00:00
|
|
|
new_operation['authorizations'] = {
|
2014-03-19 22:09:09 +00:00
|
|
|
'oauth2': [
|
|
|
|
{
|
|
|
|
'scope': scope.scope
|
|
|
|
}
|
|
|
|
],
|
2014-03-12 16:37:06 +00:00
|
|
|
}
|
|
|
|
|
2014-03-14 22:07:03 +00:00
|
|
|
internal = method_metadata(method, 'internal')
|
|
|
|
if internal is not None:
|
|
|
|
new_operation['internal'] = True
|
|
|
|
|
|
|
|
if not internal or (internal and include_internal):
|
|
|
|
operations.append(new_operation)
|
2014-03-10 22:30:41 +00:00
|
|
|
|
2014-03-11 16:57:33 +00:00
|
|
|
swagger_path = PARAM_REGEX.sub(r'{\2}', rule.rule)
|
2014-03-14 21:35:52 +00:00
|
|
|
new_resource = {
|
2014-03-10 22:30:41 +00:00
|
|
|
'path': swagger_path,
|
2014-03-11 19:20:03 +00:00
|
|
|
'description': view_class.__doc__ if view_class.__doc__ else "",
|
2014-03-10 22:30:41 +00:00
|
|
|
'operations': operations,
|
2014-03-14 21:35:52 +00:00
|
|
|
'name': fully_qualified_name(view_class),
|
|
|
|
}
|
2014-03-14 22:07:03 +00:00
|
|
|
|
2014-03-14 21:35:52 +00:00
|
|
|
related_user_res = method_metadata(view_class, 'related_user_resource')
|
|
|
|
if related_user_res is not None:
|
2014-03-14 21:42:19 +00:00
|
|
|
new_resource['quayUserRelated'] = fully_qualified_name(related_user_res)
|
2014-03-14 21:35:52 +00:00
|
|
|
|
2014-03-14 22:07:03 +00:00
|
|
|
internal = method_metadata(view_class, 'internal')
|
|
|
|
if internal is not None:
|
|
|
|
new_resource['internal'] = True
|
|
|
|
|
|
|
|
if not internal or (internal and include_internal):
|
|
|
|
apis.append(new_resource)
|
2014-03-15 03:40:41 +00:00
|
|
|
|
|
|
|
# If compact form was requested, simply return the APIs.
|
|
|
|
if compact:
|
|
|
|
return {'apis': apis}
|
|
|
|
|
2014-03-10 22:30:41 +00:00
|
|
|
swagger_data = {
|
|
|
|
'apiVersion': 'v1',
|
|
|
|
'swaggerVersion': '1.2',
|
2014-03-14 22:57:28 +00:00
|
|
|
'basePath': 'http://localhost:5000',
|
2014-03-11 03:54:55 +00:00
|
|
|
'resourcePath': '/',
|
2014-03-11 16:57:33 +00:00
|
|
|
'info': {
|
|
|
|
'title': 'Quay.io API',
|
2014-03-11 19:20:03 +00:00
|
|
|
'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>.'),
|
2014-03-11 16:57:33 +00:00
|
|
|
'termsOfServiceUrl': 'https://quay.io/tos',
|
|
|
|
'contact': 'support@quay.io',
|
|
|
|
},
|
2014-03-12 16:37:06 +00:00
|
|
|
'authorizations': {
|
|
|
|
'oauth2': {
|
2014-03-19 22:09:09 +00:00
|
|
|
'scopes': [scope._asdict() for scope in scopes.ALL_SCOPES.values()],
|
2014-03-12 16:37:06 +00:00
|
|
|
'grantTypes': {
|
|
|
|
"implicit": {
|
|
|
|
"tokenName": "access_token",
|
|
|
|
"loginEndpoint": {
|
2014-03-14 22:57:28 +00:00
|
|
|
"url": "http://localhost:5000/oauth/authorize",
|
2014-03-12 16:37:06 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2014-03-10 22:30:41 +00:00
|
|
|
'apis': apis,
|
2014-03-11 03:54:55 +00:00
|
|
|
'models': models,
|
2014-03-10 22:30:41 +00:00
|
|
|
}
|
2014-03-15 03:40:41 +00:00
|
|
|
|
2014-03-10 22:30:41 +00:00
|
|
|
return swagger_data
|
|
|
|
|
2014-03-11 16:57:33 +00:00
|
|
|
@resource('/v1/discovery')
|
2014-03-12 20:31:37 +00:00
|
|
|
class DiscoveryResource(ApiResource):
|
2014-03-11 19:20:03 +00:00
|
|
|
"""Ability to inspect the API for usage information and documentation."""
|
2014-03-14 22:07:03 +00:00
|
|
|
@parse_args
|
|
|
|
@query_param('internal', 'Whether to include internal APIs.', type=truthy_bool, default=False)
|
2014-03-10 22:30:41 +00:00
|
|
|
@nickname('discovery')
|
2014-03-14 22:07:03 +00:00
|
|
|
def get(self, args):
|
2014-03-11 19:20:03 +00:00
|
|
|
""" List all of the API endpoints available in the swagger API format."""
|
2014-03-14 22:07:03 +00:00
|
|
|
return swagger_route_data(args['internal'])
|