#!/usr/bin/env python # -*- coding: utf-8 -*- """ License Copyright (C) 2013 YunoHost This program is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program; if not, see http://www.gnu.org/licenses """ """ Generate JSON specification files API """ import os import sys import yaml import json import requests def main(): with open("../share/actionsmap.yml") as f: action_map = yaml.safe_load(f) #try: # with open("/etc/yunohost/current_host", "r") as f: # domain = f.readline().rstrip() #except IOError: # domain = requests.get("http://ip.yunohost.org").text with open("../debian/changelog") as f: top_changelog = f.readline() api_version = top_changelog[top_changelog.find("(") + 1 : top_changelog.find(")")] csrf = { "name": "X-Requested-With", "in": "header", "required": True, "schema": {"type": "string", "default": "Swagger API"}, } resource_list = { "openapi": "3.0.3", "info": { "title": "YunoHost API", "description": "This is the YunoHost API used on all YunoHost instances. This API is essentially used by YunoHost Webadmin.", "version": api_version, }, "servers": [ { "url": "https://{domain}/yunohost/api", "variables": { "domain": { "default": "demo.yunohost.org", "description": "Your yunohost domain", } }, } ], "tags": [{"name": "public", "description": "Public route"}], "paths": { "/login": { "post": { "tags": ["public"], "summary": "Logs in and returns the authentication cookie", "parameters": [csrf], "requestBody": { "required": True, "content": { "multipart/form-data": { "schema": { "type": "object", "properties": { "credentials": { "type": "string", "format": "password", } }, "required": ["credentials"], } } }, }, "security": [], "responses": { "200": { "description": "Successfully login", "headers": {"Set-Cookie": {"schema": {"type": "string"}}}, } }, } }, "/installed": { "get": { "tags": ["public"], "summary": "Test if the API is working", "parameters": [], "security": [], "responses": { "200": { "description": "Successfully working", } }, } }, }, } def convert_categories(categories, parent_category=""): for category, category_params in categories.items(): if parent_category: category = f"{parent_category} {category}" if "subcategory_help" in category_params: category_params["category_help"] = category_params["subcategory_help"] if "category_help" not in category_params: category_params["category_help"] = "" resource_list["tags"].append( {"name": category, "description": category_params["category_help"]} ) for action, action_params in category_params["actions"].items(): if "action_help" not in action_params: action_params["action_help"] = "" if "api" not in action_params: continue if not isinstance(action_params["api"], list): action_params["api"] = [action_params["api"]] for i, api in enumerate(action_params["api"]): print(api) method, path = api.split(" ") method = method.lower() key_param = "" if "{" in path: key_param = path[path.find("{") + 1 : path.find("}")] resource_list["paths"].setdefault(path, {}) notes = "" operationId = f"{category}_{action}" if i > 0: operationId += f"_{i}" operation = { "tags": [category], "operationId": operationId, "summary": action_params["action_help"], "description": notes, "responses": {"200": {"description": "successful operation"}}, } if action_params.get("deprecated"): operation["deprecated"] = True operation["parameters"] = [] if method == "post": operation["parameters"] = [csrf] if "arguments" in action_params: if method in ["put", "post", "patch"]: operation["requestBody"] = { "required": True, "content": { "multipart/form-data": { "schema": { "type": "object", "properties": {}, "required": [], } } }, } for arg_name, arg_params in action_params["arguments"].items(): if "help" not in arg_params: arg_params["help"] = "" param_type = "query" allow_multiple = False required = True allowable_values = None name = str(arg_name).replace("-", "_") if name[0] == "_": required = False if "full" in arg_params: name = arg_params["full"][2:] else: name = name[2:] name = name.replace("-", "_") if "choices" in arg_params: allowable_values = arg_params["choices"] _type = "string" if "type" in arg_params: types = {"open": "file", "int": "int"} _type = types[arg_params["type"]] if ( "action" in arg_params and arg_params["action"] == "store_true" ): _type = "boolean" if "nargs" in arg_params: if arg_params["nargs"] == "*": allow_multiple = True required = False _type = "array" if arg_params["nargs"] == "+": allow_multiple = True required = True _type = "array" if arg_params["nargs"] == "?": allow_multiple = False required = False else: allow_multiple = False if name == key_param: param_type = "path" required = True allow_multiple = False if method in ["put", "post", "patch"]: schema = operation["requestBody"]["content"][ "multipart/form-data" ]["schema"] schema["properties"][name] = { "type": _type, "description": arg_params["help"], } if required: schema["required"].append(name) prop_schema = schema["properties"][name] else: parameters = { "name": name, "in": param_type, "description": arg_params["help"], "required": required, "schema": { "type": _type, }, "explode": allow_multiple, } prop_schema = parameters["schema"] operation["parameters"].append(parameters) if allowable_values is not None: prop_schema["enum"] = allowable_values if "default" in arg_params: prop_schema["default"] = arg_params["default"] if arg_params.get("metavar") == "PASSWORD": prop_schema["format"] = "password" if arg_params.get("metavar") == "MAIL": prop_schema["format"] = "mail" # Those lines seems to slow swagger ui too much # if 'pattern' in arg_params.get('extra', {}): # prop_schema['pattern'] = arg_params['extra']['pattern'][0] resource_list["paths"][path][method.lower()] = operation # Includes subcategories if "subcategories" in category_params: convert_categories(category_params["subcategories"], category) del action_map["_global"] convert_categories(action_map) openapi_json = json.dumps(resource_list) # Save the OpenAPI json with open(os.getcwd() + "/openapi.json", "w") as f: f.write(openapi_json) openapi_js = f"var openapiJSON = {openapi_json}" with open(os.getcwd() + "/openapi.js", "w") as f: f.write(openapi_js) if __name__ == "__main__": sys.exit(main())