diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..e8f045b1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +__pycache__ +.idea +logs +.venv +.vscode \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..6d9e4920 --- /dev/null +++ b/Makefile @@ -0,0 +1,2 @@ +test: + python -m unitest test/settings/test_settings.py \ No newline at end of file diff --git a/appmanage/__init__.py b/appmanage/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/appmanage/api/exception/__init__.py b/appmanage/api/exception/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/appmanage/api/exception/command_exception.py b/appmanage/api/exception/command_exception.py index 99240633..b3a9e89a 100644 --- a/appmanage/api/exception/command_exception.py +++ b/appmanage/api/exception/command_exception.py @@ -2,4 +2,9 @@ class CommandException(Exception): def __init__(self, code, message, detail): self.code = code self.message = message - self.detail = detail \ No newline at end of file + self.detail = detail + + +class MissingConfigException(CommandException): + + pass diff --git a/appmanage/api/model/__init__.py b/appmanage/api/model/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/appmanage/api/service/__init__.py b/appmanage/api/service/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/appmanage/api/settings/__init__.py b/appmanage/api/settings/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/appmanage/api/settings/settings.py b/appmanage/api/settings/settings.py new file mode 100644 index 00000000..426ec9cd --- /dev/null +++ b/appmanage/api/settings/settings.py @@ -0,0 +1,50 @@ +from api.utils.helper import Singleton + + +__all__ = ['settings'] + + +class Settings(object): + + __metaclass__ = Singleton + + def __init__(self): + self._config = {} + self.config_file = '/usr/src/app/config/settings.conf' + + def init_config_from_file(self, config_file: str=None): + if config_file: + self.config_file = config_file + try: + with open(config_file, 'r') as f: + data = f.readlines() + except Exception: + data = [] + for i in data: + i = i.replace('\n', '').replace('\r\n', '') + key, value = i.split('=') + if self._config.get(key) != value: + self._config[key] = value + + def update_setting(self, key: str, value: str): + self._config[key] = value + self.flush_config() + + def get_setting(self, key: str, default=None): + return self._config.get(key, default) + + def list_all_settings(self) -> list: + return self._config + + def delete_setting(self, key: str, value: str): + if key in self._config: + del self._config[key] + + def flush_config(self): + with open(self.config_file, 'w') as f: + for key, value in self._config.items(): + f.write(f'{key}={value}\n') + + + +settings = Settings() diff --git a/appmanage/api/utils/__init__.py b/appmanage/api/utils/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/appmanage/api/utils/const.py b/appmanage/api/utils/const.py index ea922f5a..8a4a8867 100644 --- a/appmanage/api/utils/const.py +++ b/appmanage/api/utils/const.py @@ -1,25 +1,26 @@ # 所有常量统一定义区 # 错误代码定义 -ERROR_CLIENT_PARAM_BLANK="Client.Parameter.Blank.Error" -ERROR_CLIENT_PARAM_Format="Client.Parameter.Format.Error" -ERROR_CLIENT_PARAM_NOTEXIST="Client.Parameter.Value.NotExist.Error" -ERROR_CLIENT_PARAM_REPEAT="Client.Parameter.Value.Repeat.Error" -ERROR_CONFIG_NGINX="Nginx.Configure.Error" -ERROR_SERVER_COMMAND="Server.Container.Error" -ERROR_SERVER_SYSTEM="Server.SystemError" -ERROR_SERVER_RESOURCE="Server.ResourceError" +ERROR_CLIENT_PARAM_BLANK = "Client.Parameter.Blank.Error" +ERROR_CLIENT_PARAM_Format = "Client.Parameter.Format.Error" +ERROR_CLIENT_PARAM_NOTEXIST = "Client.Parameter.Value.NotExist.Error" +ERROR_CLIENT_PARAM_REPEAT = "Client.Parameter.Value.Repeat.Error" +ERROR_CONFIG_NGINX = "Nginx.Configure.Error" +ERROR_SERVER_COMMAND = "Server.Container.Error" +ERROR_SERVER_SYSTEM = "Server.SystemError" +ERROR_SERVER_RESOURCE = "Server.ResourceError" +ERROR_SERVER_CONFIG_MISSING = "Server.Config.NotFound" # 错误信息定义 -ERRORMESSAGE_CLIENT_PARAM_BLANK="Client.Parameter.Blank.Error" -ERRORMESSAGE_CLIENT_PARAM_Format="Client.Parameter.Format.Error" -ERRORMESSAGE_CLIENT_PARAM_NOTEXIST="Client.Parameter.Value.NotExist.Error" -ERRORMESSAGE_CLIENT_PARAM_REPEAT="Client.Parameter.Value.Repeat.Error" -ERRORMESSAGE_SERVER_COMMAND="Server.Container.Error" -ERRORMESSAGE_SERVER_SYSTEM="Server.SystemError" -ERRORMESSAGE_SERVER_RESOURCE="Server.ResourceError" -ERRORMESSAGE_SERVER_VERSION_NOTSUPPORT="Server.Version.NotSupport" -ERRORMESSAGE_SERVER_VERSION_NEEDUPGRADE="Server.Version.NeedUpgradeCore" +ERRORMESSAGE_CLIENT_PARAM_BLANK = "Client.Parameter.Blank.Error" +ERRORMESSAGE_CLIENT_PARAM_Format = "Client.Parameter.Format.Error" +ERRORMESSAGE_CLIENT_PARAM_NOTEXIST = "Client.Parameter.Value.NotExist.Error" +ERRORMESSAGE_CLIENT_PARAM_REPEAT = "Client.Parameter.Value.Repeat.Error" +ERRORMESSAGE_SERVER_COMMAND = "Server.Container.Error" +ERRORMESSAGE_SERVER_SYSTEM = "Server.SystemError" +ERRORMESSAGE_SERVER_RESOURCE = "Server.ResourceError" +ERRORMESSAGE_SERVER_VERSION_NOTSUPPORT = "Server.Version.NotSupport" +ERRORMESSAGE_SERVER_VERSION_NEEDUPGRADE = "Server.Version.NeedUpgradeCore" # 应用状态定义 # 应用启动中 installing @@ -33,7 +34,7 @@ APP_STATUS_RESTARTING = "restarting" # 应用错误 failed APP_STATUS_FAILED = "failed" -NGINX_URL="http://websoft9-nginxproxymanager:81" -#ARTIFACT_URL="https://artifact.azureedge.net/release/websoft9" -ARTIFACT_URL="https://w9artifact.blob.core.windows.net/release/websoft9" -ARTIFACT_URL_DEV="https://w9artifact.blob.core.windows.net/dev/websoft9" +NGINX_URL = "http://websoft9-nginxproxymanager:81" +# ARTIFACT_URL="https://artifact.azureedge.net/release/websoft9" +ARTIFACT_URL = "https://w9artifact.blob.core.windows.net/release/websoft9" +ARTIFACT_URL_DEV = "https://w9artifact.blob.core.windows.net/dev/websoft9" diff --git a/appmanage/api/utils/helper.py b/appmanage/api/utils/helper.py new file mode 100644 index 00000000..d6cc857e --- /dev/null +++ b/appmanage/api/utils/helper.py @@ -0,0 +1,6 @@ +class Singleton(type): + _instances = {} + def __call__(cls, *args, **kwargs): + if cls not in cls._instances: + cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs) + return cls._instances[cls] diff --git a/appmanage/api/v1/__init__.py b/appmanage/api/v1/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/appmanage/api/v1/api.py b/appmanage/api/v1/api.py index 1dace987..c8430e37 100644 --- a/appmanage/api/v1/api.py +++ b/appmanage/api/v1/api.py @@ -1,6 +1,6 @@ from fastapi import APIRouter -from api.v1.routers import health,apps +from api.v1.routers import health, apps def get_api(): diff --git a/appmanage/api/v1/routers/__init__.py b/appmanage/api/v1/routers/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/appmanage/api/v1/routers/apps.py b/appmanage/api/v1/routers/apps.py index 9cc05f4f..343bc81f 100644 --- a/appmanage/api/v1/routers/apps.py +++ b/appmanage/api/v1/routers/apps.py @@ -11,6 +11,7 @@ from api.service import manage, db from api.utils import shell_execute, const from api.utils.common_log import myLogger from api.exception.command_exception import CommandException +from api.settings.settings import settings router = APIRouter() @@ -76,10 +77,17 @@ rd_appstore = rd_s + appstore_update + rd_e rd_auto_list = rd_s + auto + rd_e rd_user_list = rd_s + user + rd_e rd_updateuser_list=rd_s + updateuser + rd_e + + +class SettingItem(BaseModel): + + key: str = Field(description="配置项") + value: str = Field(description="配置项的取值") + @router.api_route("/AppStatus", methods=["GET", "POST"], summary="获取指定APP的信息", response_description=rd_status, response_model=Response) -def AppStatus(request: Request,app_id: Optional[str] = Query(default=None, description="应用ID")): +def AppStatus(request: Request, app_id: Optional[str] = Query(default=None, description="应用ID")): try: myLogger.info_logger("Receive request: /AppStatus") get_headers(request) @@ -526,6 +534,27 @@ def AppUpdateUser(request: Request,user_name: Optional[str] = Query(default=None return response + +@router.api_route("/AppListSettings", methods=['GET', 'POST'], summary="获取配置信息") +def list_settings(): + items = settings.list_all_settings() + return [{ + 'key': key, + 'value': value + } for key, value in items.items()] + + +@router.api_route("/AppUpdateSettings", methods=['GET', 'POST'], summary="创建或者更新配置信息") +def create_or_update_settings(item: SettingItem): + settings.update_setting(item.key, item.value) + + + +@router.api_route("/AppDeleteSettings", methods=['GET', 'POST'], summary="删除配置信息") +def delete_settings(item: SettingItem): + settings.delete_setting(item.key, item.value) + + def get_headers(request): headers = request.headers try: diff --git a/appmanage/main.py b/appmanage/main.py index 46a4cc59..a1b43dcb 100644 --- a/appmanage/main.py +++ b/appmanage/main.py @@ -1,7 +1,6 @@ -import api.v1.api as api_router_v1 +import argparse import uvicorn -from api.utils.common_log import myLogger -from api.utils import shell_execute + from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from fastapi.staticfiles import StaticFiles @@ -11,10 +10,19 @@ from fastapi.openapi.docs import ( get_swagger_ui_oauth2_redirect_html, ) +import api.v1.api as api_router_v1 + +from api.utils.common_log import myLogger +from api.utils import shell_execute +from api.settings.settings import settings + + myLogger.info_logger("Start server...") app = FastAPI(docs_url=None, redoc_url=None, openapi_url="/") -def get_app(): + +def get_app(): + settings.init_config_from_file() origins = [ "http://localhost", "http://localhost:9090", @@ -45,6 +53,7 @@ async def custom_swagger_ui_html(): async def swagger_ui_redirect(): return get_swagger_ui_oauth2_redirect_html() + @app.get("/redoc", include_in_schema=False) async def redoc_html(): return get_redoc_html( @@ -54,4 +63,9 @@ async def redoc_html(): ) if __name__ == "__main__": - uvicorn.run("main:get_app", host='0.0.0.0', port=5000, reload=True) \ No newline at end of file + parser = argparse.ArgumentParser(description='websoft9') + parser.add_argument("--port", type=int, dest='port', default=5000, metavar="port") + parser.add_argument("--config", type=str, dest="config_file", required=True) + args = parser.parse_args() + settings.init_config_from_file(config_file=args.config_file) + uvicorn.run("main:get_app", host='0.0.0.0', port=args.port, reload=True) \ No newline at end of file diff --git a/appmanage/test/README.md b/test/README.md similarity index 100% rename from appmanage/test/README.md rename to test/README.md diff --git a/test/__init__.py b/test/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/test/settings/__init__.py b/test/settings/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/test/settings/test_settings.py b/test/settings/test_settings.py new file mode 100644 index 00000000..124db0a5 --- /dev/null +++ b/test/settings/test_settings.py @@ -0,0 +1,46 @@ +import os +import tempfile +import unittest + +from appmanage.api.settings.settings import settings + + +class TestSettings(unittest.TestCase): + fd = None + + @classmethod + def setUpClass(cls): + fd = tempfile.NamedTemporaryFile("w") + print(fd.name) + fd.write("a=b\nc=d\n") + fd.flush() + settings.init_config_from_file(fd.name) + + def test_get_config(self): + self.assertEqual(settings.get_setting("a"), "b") + self.assertTrue(settings.get_setting("e") is None) + + def test_update_config(self): + self.assertEqual(settings.get_setting("a"), "b") + settings.update_setting("a", "i") + self.assertEqual(settings.get_setting("a"), "i") + + def test_list_settings(self): + data = settings.list_all_settings() + self.assertTrue(data is not None) + + def test_delete_config(self): + settings.update_setting("x", "y") + v = settings.get_setting("x") + self.assertTrue(v is not None) + settings.delete_setting("x", v) + self.assertTrue(settings.get_setting("x") is None) + + @classmethod + def tearDownClass(cls): + if cls.fd: + cls.fd.close() + + +if __name__ == '__main__': + unittest.main()