|
1 | 1 | import logging |
2 | 2 | import uvicorn |
| 3 | +import json |
| 4 | +import os |
3 | 5 |
|
4 | 6 | from fastapi import FastAPI, Request |
5 | 7 | from fastapi.middleware.cors import CORSMiddleware |
| 8 | +from fastapi.openapi.docs import get_swagger_ui_html |
| 9 | +from fastapi.responses import HTMLResponse |
6 | 10 |
|
7 | 11 | from spaceone.core import config |
8 | 12 | from spaceone.core.logger import set_logger |
@@ -130,10 +134,107 @@ def _init_fast_api(): |
130 | 134 | ) |
131 | 135 |
|
132 | 136 |
|
| 137 | +def _get_all_services_from_openapi_json_files(openapi_json_path): |
| 138 | + services = [] |
| 139 | + openapi_json_files = os.listdir(openapi_json_path) |
| 140 | + for openapi_json_file in openapi_json_files: |
| 141 | + services.append('_'.join(openapi_json_file.split('_')[:-1]).lower()) |
| 142 | + return services |
| 143 | + |
| 144 | + |
| 145 | +def _sort_services(services): |
| 146 | + return sorted(services, key=lambda x: ('identity' not in x, 'inventory' not in x, 'cost-analysis' not in x, |
| 147 | + 'monitoring' not in x, 'notification' not in x, 'repository' not in x, x)) |
| 148 | + |
| 149 | + |
| 150 | +def _create_openapi_json(app, service_name): |
| 151 | + swagger_path = config.get_global('EXTENSION_SWAGGER_PATH') |
| 152 | + swagger_path = os.path.join(swagger_path, f"{service_name.replace('-', '_')}_openapi.json") |
| 153 | + try: |
| 154 | + with open(swagger_path, 'r') as f: |
| 155 | + custom_openapi_schema = json.loads(f.read()) |
| 156 | + custom_openapi_schema['openapi'] = app.openapi().get('openapi') |
| 157 | + description = custom_openapi_schema['info']['summary'] |
| 158 | + app.openapi()['info']['description'] += f"| **{service_name.replace('-', ' ').title()}** | {description} | [/{service_name}/docs](/{service_name}/docs) |\n" |
| 159 | + |
| 160 | + with open(swagger_path, 'w') as f: |
| 161 | + json.dump(custom_openapi_schema, f, indent=2) |
| 162 | + except Exception as e: |
| 163 | + _LOGGER.error(f'[_create_openapi_json] {swagger_path} : {e}', exc_info=True) |
| 164 | + |
| 165 | + |
| 166 | +def _override_openapi(app): |
| 167 | + extension_swagger_path = config.get_global('EXTENSION_SWAGGER_PATH') |
| 168 | + if not os.path.exists(extension_swagger_path): |
| 169 | + _LOGGER.info(f'[_override_openapi] Extension Swagger Path is not exists. (path = {extension_swagger_path})') |
| 170 | + return app |
| 171 | + |
| 172 | + services = _get_all_services_from_openapi_json_files(extension_swagger_path) |
| 173 | + services = _sort_services(services) |
| 174 | + _openapi_info = app.openapi().get('info') |
| 175 | + _openapi_version = app.openapi().get('openapi') |
| 176 | + |
| 177 | + app.openapi()['info']['description'] += "\n<br><br>\n" |
| 178 | + app.openapi()['info']['description'] += "\n## List of Services\n" |
| 179 | + app.openapi()['info']['description'] += "\n[Home](/docs)\n" |
| 180 | + app.openapi()['info']['description'] += "| **Service** | **Description** | **URL** |\n" |
| 181 | + app.openapi()['info']['description'] += "|:---|:--- |:---|\n" |
| 182 | + |
| 183 | + for service in services: |
| 184 | + service = service.replace('_', '-') |
| 185 | + _create_openapi_json(app, service_name=service) |
| 186 | + build_docs(app, prefix=f"/{service}", service_name=service) |
| 187 | + |
| 188 | + app.openapi()['info'][ |
| 189 | + 'description'] += "| **Console API** | Service that offers features exclusive to the Console API. | [/docs](/docs#console-api%20%3E%20api) |\n" |
| 190 | + |
| 191 | + return app |
| 192 | + |
| 193 | + |
| 194 | +def build_docs( |
| 195 | + app: FastAPI, |
| 196 | + prefix: str, |
| 197 | + service_name: str |
| 198 | +) -> None: |
| 199 | + async def get_openapi(): |
| 200 | + swagger_path = config.get_global('EXTENSION_SWAGGER_PATH') |
| 201 | + swagger_path = os.path.join(swagger_path, f"{service_name.replace('-','_')}_openapi.json") |
| 202 | + with open(swagger_path, 'r') as f: |
| 203 | + custom_openapi_schema = json.loads(f.read()) |
| 204 | + return custom_openapi_schema |
| 205 | + |
| 206 | + get_openapi.__name__ = get_openapi.__name__ + prefix |
| 207 | + app.add_api_route(prefix + "/openapi.json", get_openapi, include_in_schema=False) |
| 208 | + |
| 209 | + async def swagger_ui_html() -> HTMLResponse: |
| 210 | + return get_swagger_ui_html( |
| 211 | + openapi_url=prefix + "/openapi.json", |
| 212 | + title=f'{service_name.title().replace("-"," ")} API' + ' - Swagger UI', |
| 213 | + oauth2_redirect_url=app.swagger_ui_oauth2_redirect_url, |
| 214 | + init_oauth=app.swagger_ui_init_oauth, |
| 215 | + ) |
| 216 | + |
| 217 | + swagger_ui_html.__name__ = swagger_ui_html.__name__ + prefix |
| 218 | + app.add_api_route(prefix + "/docs", swagger_ui_html, include_in_schema=False) |
| 219 | + |
| 220 | + |
| 221 | +def _get_all_services_list(app): |
| 222 | + services = [] |
| 223 | + for route in app.routes: |
| 224 | + path = route.path.split('/') |
| 225 | + if len(path) == 4: |
| 226 | + services.append(path[1].replace('-', '_')) |
| 227 | + |
| 228 | + services = list(set(services)) |
| 229 | + sorted_services = sorted(services, key=lambda x: ('identity' not in x, 'inventory' not in x, 'cost_analysis' not in x, x)) |
| 230 | + return sorted_services |
| 231 | + |
| 232 | + |
133 | 233 | def fast_api_app(): |
134 | 234 | app = _init_fast_api() |
135 | 235 | app = _add_middlewares(app) |
136 | 236 | app = _include_routers(app) |
| 237 | + app = _override_openapi(app) |
137 | 238 | return app |
138 | 239 |
|
139 | 240 |
|
|
0 commit comments