From: Ricardo Dias Date: Thu, 3 May 2018 14:26:37 +0000 (+0100) Subject: mgr/dashboard: Swagger-UI based Dashboard REST API page X-Git-Tag: v14.0.1~1170^2 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=refs%2Fpull%2F22282%2Fhead;p=ceph.git mgr/dashboard: Swagger-UI based Dashboard REST API page Fixes: http://tracker.ceph.com/issues/23898 Signed-off-by: Ricardo Dias --- diff --git a/src/pybind/mgr/dashboard/controllers/docs.py b/src/pybind/mgr/dashboard/controllers/docs.py new file mode 100644 index 000000000000..b61bea997bbb --- /dev/null +++ b/src/pybind/mgr/dashboard/controllers/docs.py @@ -0,0 +1,265 @@ +# -*- coding: utf-8 -*- +from __future__ import absolute_import + +import cherrypy + +from . import Controller, BaseController, AuthRequired, Endpoint, ENDPOINT_MAP +from .. import logger + + +@Controller('/docs') +@AuthRequired() +class Docs(BaseController): + + @classmethod + def _gen_tags(cls, all_endpoints): + ctrl_names = set() + for endpoints in ENDPOINT_MAP.values(): + for endpoint in endpoints: + if endpoint.is_api or all_endpoints: + ctrl_names.add(endpoint.group) + + return [{'name': name, 'description': ""} + for name in sorted(ctrl_names)] + + @classmethod + def _gen_type(cls, param): + # pylint: disable=too-many-return-statements + """ + Generates the type of parameter based on its name and default value, + using very simple heuristics. + """ + param_name = param['name'] + def_value = param['default'] if 'default' in param else None + if param_name.startswith("is_"): + return "boolean" + elif "size" in param_name: + return "integer" + elif "count" in param_name: + return "integer" + elif "num" in param_name: + return "integer" + elif isinstance(def_value, bool): + return "boolean" + elif isinstance(def_value, int): + return "integer" + return "string" + + @classmethod + def _gen_body_param(cls, body_params): + required = [p['name'] for p in body_params if p['required']] + + props = {} + for p in body_params: + props[p['name']] = { + 'type': cls._gen_type(p) + } + if 'default' in p: + props[p['name']]['default'] = p['default'] + + if not props: + return None + + return { + 'in': "body", + 'name': "body", + 'description': "", + 'required': True, + 'schema': { + 'type': "object", + 'required': required, + 'properties': props + } + } + + @classmethod + def _gen_responses_descriptions(cls, method): + resp = { + '400': { + "description": "Operation exception. Please check the " + "response body for details." + }, + '401': { + "description": "Unauthenticated access. Please login first." + }, + '403': { + "description": "Unauthorized access. Please check your " + "permissions." + }, + '500': { + "description": "Unexpected error. Please check the " + "response body for the stack trace." + } + } + if method.lower() == 'get': + resp['200'] = {'description': "OK"} + if method.lower() == 'post': + resp['201'] = {'description': "Resource created."} + if method.lower() == 'put': + resp['200'] = {'description': "Resource updated."} + if method.lower() == 'delete': + resp['204'] = {'description': "Resource deleted."} + if method.lower() in ['post', 'put', 'delete']: + resp['202'] = {'description': "Operation is still executing." + " Please check the task queue."} + + return resp + + @classmethod + def _gen_param(cls, param, ptype): + res = { + 'name': param['name'], + 'in': ptype, + 'type': cls._gen_type(param) + } + if param['required']: + res['required'] = True + elif param['default'] is None: + res['allowEmptyValue'] = True + else: + res['default'] = param['default'] + return res + + def _gen_spec(self, all_endpoints=False, baseUrl=""): + if all_endpoints: + baseUrl = "" + METHOD_ORDER = ['get', 'post', 'put', 'delete'] + host = cherrypy.request.base + host = host[host.index(':')+3:] + logger.debug("DOCS: Host: %s", host) + + paths = {} + for path, endpoints in sorted(list(ENDPOINT_MAP.items()), + key=lambda p: p[0]): + methods = {} + skip = False + + endpoint_list = sorted(endpoints, key=lambda e: + METHOD_ORDER.index(e.method.lower())) + for endpoint in endpoint_list: + if not endpoint.is_api and not all_endpoints: + skip = True + break + + method = endpoint.method + params = [] + params.extend([self._gen_param(p, 'path') + for p in endpoint.path_params]) + params.extend([self._gen_param(p, 'query') + for p in endpoint.query_params]) + + if method.lower() in ['post', 'put']: + body_params = self._gen_body_param(endpoint.body_params) + if body_params: + params.append(body_params) + + methods[method.lower()] = { + 'tags': [endpoint.group], + 'summary': "", + 'consumes': [ + "application/json" + ], + 'produces': [ + "application/json" + ], + 'parameters': params, + 'responses': self._gen_responses_descriptions(method), + "security": [""] + } + + if not skip: + paths[path[len(baseUrl):]] = methods + + if not baseUrl: + baseUrl = "/" + spec = { + 'swagger': "2.0", + 'info': { + 'description': "Please note that this API is not an official " + "Ceph REST API to be used by third-party " + "applications. It's primary purpose is to serve" + " the requirements of the Ceph Dashboard and is" + " subject to change at any time. Use at your " + "own risk.", + 'version': "v1", + 'title': "Ceph-Dashboard REST API" + }, + 'host': host, + 'basePath': baseUrl, + 'tags': self._gen_tags(all_endpoints), + 'schemes': ["https"], + 'paths': paths + } + + return spec + + @Endpoint(path="api.json") + def api_json(self): + return self._gen_spec(False, "/api") + + @Endpoint(path="api-all.json") + def api_all_json(self): + return self._gen_spec(True, "/api") + + @Endpoint(json_response=False) + def __call__(self, all_endpoints=False): + base = cherrypy.request.base + if all_endpoints: + spec_url = "{}/docs/api-all.json".format(base) + else: + spec_url = "{}/docs/api.json".format(base) + page = """ + + + + + + + + + + + +
+ + + + + + """.format(spec_url) + + return page