From 63bb951b3f91e149fb30aec7774b95503b581d8a Mon Sep 17 00:00:00 2001 From: zhaojing1987 Date: Thu, 2 Nov 2023 08:55:19 +0800 Subject: [PATCH] update apphub --- apphub/src/apphub.egg-info/PKG-INFO | 3 + apphub/src/apphub.egg-info/SOURCES.txt | 10 ++ .../src/apphub.egg-info/dependency_links.txt | 1 + apphub/src/apphub.egg-info/entry_points.txt | 2 + apphub/src/apphub.egg-info/requires.txt | 1 + apphub/src/apphub.egg-info/top_level.txt | 1 + apphub/src/config/config.ini | 2 +- apphub/src/external/portainer_api.py | 114 +++++++++++------- apphub/src/main.py | 4 +- apphub/src/schemas/appResponse.py | 13 +- apphub/src/services/app_manager.py | 23 ++-- apphub/src/services/portainer_manager.py | 2 +- apphub/src/services/proxy_manager.py | 14 +-- 13 files changed, 121 insertions(+), 69 deletions(-) create mode 100644 apphub/src/apphub.egg-info/PKG-INFO create mode 100644 apphub/src/apphub.egg-info/SOURCES.txt create mode 100644 apphub/src/apphub.egg-info/dependency_links.txt create mode 100644 apphub/src/apphub.egg-info/entry_points.txt create mode 100644 apphub/src/apphub.egg-info/requires.txt create mode 100644 apphub/src/apphub.egg-info/top_level.txt diff --git a/apphub/src/apphub.egg-info/PKG-INFO b/apphub/src/apphub.egg-info/PKG-INFO new file mode 100644 index 00000000..bc6a0c19 --- /dev/null +++ b/apphub/src/apphub.egg-info/PKG-INFO @@ -0,0 +1,3 @@ +Metadata-Version: 2.1 +Name: apphub +Version: 0.2 diff --git a/apphub/src/apphub.egg-info/SOURCES.txt b/apphub/src/apphub.egg-info/SOURCES.txt new file mode 100644 index 00000000..0a6e8238 --- /dev/null +++ b/apphub/src/apphub.egg-info/SOURCES.txt @@ -0,0 +1,10 @@ +README.md +setup.py +src/apphub.egg-info/PKG-INFO +src/apphub.egg-info/SOURCES.txt +src/apphub.egg-info/dependency_links.txt +src/apphub.egg-info/entry_points.txt +src/apphub.egg-info/requires.txt +src/apphub.egg-info/top_level.txt +src/cli/__init__.py +src/cli/apphub_cli.py \ No newline at end of file diff --git a/apphub/src/apphub.egg-info/dependency_links.txt b/apphub/src/apphub.egg-info/dependency_links.txt new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/apphub/src/apphub.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/apphub/src/apphub.egg-info/entry_points.txt b/apphub/src/apphub.egg-info/entry_points.txt new file mode 100644 index 00000000..e365cb8a --- /dev/null +++ b/apphub/src/apphub.egg-info/entry_points.txt @@ -0,0 +1,2 @@ +[console_scripts] +apphub = cli.apphub_cli:cli diff --git a/apphub/src/apphub.egg-info/requires.txt b/apphub/src/apphub.egg-info/requires.txt new file mode 100644 index 00000000..dca9a909 --- /dev/null +++ b/apphub/src/apphub.egg-info/requires.txt @@ -0,0 +1 @@ +click diff --git a/apphub/src/apphub.egg-info/top_level.txt b/apphub/src/apphub.egg-info/top_level.txt new file mode 100644 index 00000000..573c0c4f --- /dev/null +++ b/apphub/src/apphub.egg-info/top_level.txt @@ -0,0 +1 @@ +cli diff --git a/apphub/src/config/config.ini b/apphub/src/config/config.ini index d301aefd..e1ed25eb 100755 --- a/apphub/src/config/config.ini +++ b/apphub/src/config/config.ini @@ -22,7 +22,7 @@ path = /websoft9/library/apps path = /websoft9/media/json/ [api_key] -key = c7c1c6876cbda1fb8f3a991f4893037e1d3f7a7da9e12a23b64c403e7054240f +key = 9deeaf0d9f6f78c289f199a2631dc793b823d24024258385faaf833ea31e4ff6 [domain] wildcard_domain = test.websoft9.cn diff --git a/apphub/src/external/portainer_api.py b/apphub/src/external/portainer_api.py index f3b6efa9..ae18dc3d 100755 --- a/apphub/src/external/portainer_api.py +++ b/apphub/src/external/portainer_api.py @@ -1,7 +1,46 @@ import json +import threading from src.core.apiHelper import APIHelper from src.core.config import ConfigManager +from functools import wraps +from src.core.logger import logger +from src.core.exception import CustomException + + +class JWTManager: + jwt_token = None + token_lock = threading.Lock() + + @classmethod + def get_token(cls): + with cls.token_lock: + if cls.jwt_token is None: + cls.refresh_token() + return cls.jwt_token + + @classmethod + def refresh_token(cls): + username = ConfigManager().get_value("portainer", "user_name") + password = ConfigManager().get_value("portainer", "user_pwd") + api = APIHelper( + ConfigManager().get_value("portainer", "base_url"), + { + "Content-Type": "application/json", + }, + ) + token_response = api.post( + path="auth", + json={ + "username": username, + "password": password, + }, + ) + if token_response.status_code == 200: + cls.jwt_token = token_response.json()['jwt'] + else: + logger.error(f"Error Calling Portainer API: {token_response.status_code}:{token_response.text}") + raise CustomException() class PortainerAPI: @@ -29,6 +68,8 @@ class PortainerAPI: remove_volume_by_name(endpointId,volume_name): Remove volumes by name """ + jwt_token = None + def __init__(self): """ Initialize the PortainerAPI instance @@ -37,38 +78,25 @@ class PortainerAPI: ConfigManager().get_value("portainer", "base_url"), { "Content-Type": "application/json", + # "Authorization": f"Bearer {JWTManager.get_token()}", }, ) - def set_jwt_token(self, jwt_token): - """ - Set JWT token + def auto_refresh_token(func): + @wraps(func) + def wrapper(self, *args, **kwargs): + self.api.headers["Authorization"] = f"Bearer {JWTManager.get_token()}" + response = func(self, *args, **kwargs) + if response.status_code == 401: # If Unauthorized + JWTManager.refresh_token() # Refresh the token + self.api.headers["Authorization"] = f"Bearer {JWTManager.get_token()}" + response = func(self, *args, **kwargs) # Retry the request + # response.raise_for_status() # This will raise an exception if the response contains an HTTP error status after retrying. + return response + return wrapper - Args: - jwt_token (str): JWT token - """ - self.api.headers["Authorization"] = f"Bearer {jwt_token}" - def get_jwt_token(self, username: str, password: str): - """ - Get JWT token - - Args: - username (str): Username - password (str): Password - - Returns: - Response: Response from Portainer API - """ - return self.api.post( - path="auth", - headers={"Content-Type": "application/json"}, - json={ - "password": password, - "username": username, - }, - ) - + @auto_refresh_token def get_endpoints(self,start: int = 0,limit: int = 1000): """ Get endpoints @@ -84,6 +112,7 @@ class PortainerAPI: }, ) + @auto_refresh_token def get_endpoint_by_id(self, endpointId: int): """ Get endpoint by ID @@ -96,6 +125,7 @@ class PortainerAPI: """ return self.api.get(path=f"endpoints/{endpointId}") + @auto_refresh_token def create_endpoint(self, name: str, EndpointCreationType: int = 1): """ Create an endpoint @@ -113,6 +143,7 @@ class PortainerAPI: params={"Name": name, "EndpointCreationType": EndpointCreationType}, ) + @auto_refresh_token def get_stacks(self, endpointId: int): """ Get stacks @@ -132,6 +163,7 @@ class PortainerAPI: }, ) + @auto_refresh_token def get_stack_by_id(self, stackID: int): """ Get stack by ID @@ -144,6 +176,7 @@ class PortainerAPI: """ return self.api.get(path=f"stacks/{stackID}") + @auto_refresh_token def remove_stack(self, stackID: int, endpointId: int): """ Remove a stack @@ -159,6 +192,7 @@ class PortainerAPI: path=f"stacks/{stackID}", params={"endpointId": endpointId} ) + @auto_refresh_token def create_stack_standlone_repository(self, stack_name: str, endpointId: int, repositoryURL: str,usr_name:str,usr_password:str): """ Create a stack from a standalone repository @@ -184,6 +218,7 @@ class PortainerAPI: }, ) + @auto_refresh_token def up_stack(self, stackID: int, endpointId: int): """ Up a stack @@ -199,6 +234,7 @@ class PortainerAPI: path=f"stacks/{stackID}/start", params={"endpointId": endpointId} ) + @auto_refresh_token def down_stack(self, stackID: int, endpointId: int): """ Down a stack @@ -214,21 +250,7 @@ class PortainerAPI: path=f"stacks/{stackID}/stop", params={"endpointId": endpointId} ) - def redeploy_stack(self, stackID: int, endpointId: int): - """ - Redeploy a stack - - Args: - stackID (int): Stack ID - endpointId (int): Endpoint ID - - Returns: - Response: Response from Portainer API - """ - return self.api.post( - path=f"stacks/{stackID}/redeploy", params={"endpointId": endpointId} - ) - + @auto_refresh_token def get_volumes(self, endpointId: int,dangling: bool): """ Get volumes in endpoint @@ -246,6 +268,7 @@ class PortainerAPI: } ) + @auto_refresh_token def remove_volume_by_name(self, endpointId: int,volume_name:str): """ Remove volumes by name @@ -258,6 +281,7 @@ class PortainerAPI: path=f"endpoints/{endpointId}/docker/volumes/{volume_name}", ) + @auto_refresh_token def get_containers(self, endpointId: int): """ Get containers in endpoint @@ -272,6 +296,7 @@ class PortainerAPI: } ) + @auto_refresh_token def get_containers_by_stackName(self, endpointId: int,stack_name:str): """ Get containers in endpoint @@ -289,6 +314,7 @@ class PortainerAPI: } ) + @auto_refresh_token def get_container_by_id(self, endpointId: int, container_id: str): """ Get container by ID @@ -301,6 +327,7 @@ class PortainerAPI: path=f"endpoints/{endpointId}/docker/containers/{container_id}/json", ) + @auto_refresh_token def stop_container(self, endpointId: int, container_id: str): """ Stop container @@ -313,6 +340,7 @@ class PortainerAPI: path=f"endpoints/{endpointId}/docker/containers/{container_id}/stop", ) + @auto_refresh_token def start_container(self, endpointId: int, container_id: str): """ Start container @@ -325,6 +353,7 @@ class PortainerAPI: path=f"endpoints/{endpointId}/docker/containers/{container_id}/start", ) + @auto_refresh_token def restart_container(self, endpointId: int, container_id: str): """ Restart container @@ -337,6 +366,7 @@ class PortainerAPI: path=f"endpoints/{endpointId}/docker/containers/{container_id}/restart", ) + @auto_refresh_token def redeploy_stack(self, stackID: int, endpointId: int,pullImage:bool,user_name:str,user_password:str ): return self.api.put( path=f"stacks/{stackID}/git/redeploy", diff --git a/apphub/src/main.py b/apphub/src/main.py index e783140d..ea498879 100755 --- a/apphub/src/main.py +++ b/apphub/src/main.py @@ -25,10 +25,9 @@ stdout_handler = logging.StreamHandler(sys.stdout) # 将日志处理器添加到 Uvicorn 的 logger uvicorn_logger.addHandler(stdout_handler) - uvicorn_logger.setLevel(logging.INFO) -API_KEY = ConfigManager().get_value("api_key","key") + API_KEY_NAME = "api_key" api_key_header = APIKeyHeader(name=API_KEY_NAME, auto_error=False) @@ -42,6 +41,7 @@ async def verify_key(request: Request, api_key_header: str = Security(api_key_he message="Invalid Request", details="No API Key provided" ) + API_KEY = ConfigManager().get_value("api_key","key") if api_key_header != API_KEY: logger.error(f"Invalid API Key: {api_key_header}") diff --git a/apphub/src/schemas/appResponse.py b/apphub/src/schemas/appResponse.py index cd40a4d3..982252dd 100755 --- a/apphub/src/schemas/appResponse.py +++ b/apphub/src/schemas/appResponse.py @@ -1,20 +1,21 @@ from pydantic import BaseModel from typing import List, Any from pydantic import BaseModel, Field +from typing import Optional class AppResponse(BaseModel): app_id: str=Field("", description="App ID",example="wordpress") endpointId: int=Field(-1, description="Endpoint ID(-1:Not install on app store)",example=1) - app_name: str=Field("", description="App name",example="wordpress") - app_port: int=Field(0, description="App port",example=80) - app_dist: str=Field("", description="App dist",example="community") - app_version: str=Field("", description="App version",example="1.0.0") + app_name: Optional[str]=Field(None, description="App name",example="wordpress") + app_port: Optional[int] = Field(None, description="App port", example=80) + app_dist: Optional[str]=Field(None, description="App dist",example="community") + app_version: Optional[str]=Field(None, description="App version",example="1.0.0") app_official: bool=Field(True, description="App official",example=True) proxy_enabled: bool=Field(False, description="Proxy enabled",example=False) status: int=Field(0, description="App status(0:unknown,1:active,2:inactive)",example=0) - creationDate: int=Field(0, description="Creation date",example=0) + creationDate: Optional[int]=Field(None, description="Creation date",example=0) domain_names: List[dict]=Field([], description="Domain names") - env: List[str] = Field([], description="Environment variables") + env: dict[str, Any] = Field({}, description="Environment variables") gitConfig: dict[str, Any] = Field({}, description="Git configuration") containers: List[dict] = Field([], description="Containers") volumes: List[dict] = Field([], description="Volumes") diff --git a/apphub/src/services/app_manager.py b/apphub/src/services/app_manager.py index 7332f615..6d6605b9 100755 --- a/apphub/src/services/app_manager.py +++ b/apphub/src/services/app_manager.py @@ -21,7 +21,6 @@ from src.utils.password_generator import PasswordGenerator class AppManger: def get_catalog_apps(self,locale:str): - logger.access(f"Get catalog apps: {locale}") try: # Get the app media path base_path = ConfigManager().get_value("app_media", "path") @@ -153,6 +152,8 @@ class AppManger: # Get the main container main_container_id = None + app_env = [] + app_env_format = {} # format app_env to dict for container in app_containers: if f"/{app_id}" in container.get("Names", []): main_container_id = container.get("Id", "") @@ -160,15 +161,17 @@ class AppManger: if main_container_id: # Get the main container info main_container_info = portainerManager.get_container_by_id(endpointId, main_container_id) - # Get the env - app_env = main_container_info.get("Config", {}).get("Env", []) + # Get the env + app_env = main_container_info.get("Config", {}).get("Env", []) # Get http port from env app_http_port = None app_name = None app_dist = None + app_version = None for item in app_env: key, value = item.split("=", 1) + app_env_format[key] = value if key == "APP_HTTP_PORT": app_http_port = value elif key == "APP_NAME": @@ -205,7 +208,7 @@ class AppManger: gitConfig = gitConfig, containers = app_containers, volumes = app_volumes, - env = app_env + env = app_env_format ) return appResponse else: @@ -224,7 +227,7 @@ class AppManger: gitConfig = gitConfig, containers = [], volumes = app_volumes, - env = [] + env = {} ) return appResponse @@ -392,7 +395,7 @@ class AppManger: raise CustomException( status_code=400, message="Invalid Request", - details=f"{app_id} is empty, can not uninstall it,you can remove it" + details=f"{app_id} is inactive, can not uninstall it,you can remove it" ) if purge_data: @@ -454,7 +457,7 @@ class AppManger: raise CustomException( status_code=400, message="Invalid Request", - details=f"{app_id} is not empty, please uninstall it first" + details=f"{app_id} is not inactive, please uninstall it first" ) # Check the proxy is exists proxyManager = ProxyManager() @@ -497,7 +500,7 @@ class AppManger: raise CustomException( status_code=400, message="Invalid Request", - details=f"{app_id} is empty, can not start it,you can redeploy it" + details=f"{app_id} is inactive, can not start it,you can redeploy it" ) portainerManager.start_stack(app_id,endpointId) @@ -528,7 +531,7 @@ class AppManger: raise CustomException( status_code=400, message="Invalid Request", - details=f"{app_id} is empty, can not stop it,you can redeploy it" + details=f"{app_id} is inactive, can not stop it,you can redeploy it" ) portainerManager.stop_stack(app_id,endpointId) @@ -558,7 +561,7 @@ class AppManger: raise CustomException( status_code=400, message="Invalid Request", - details=f"{app_id} is empty, can not restart it,you can redeploy it" + details=f"{app_id} is inactive, can not restart it,you can redeploy it" ) portainerManager.restart_stack(app_id,endpointId) diff --git a/apphub/src/services/portainer_manager.py b/apphub/src/services/portainer_manager.py index aaaea79d..8b600b2e 100755 --- a/apphub/src/services/portainer_manager.py +++ b/apphub/src/services/portainer_manager.py @@ -32,7 +32,7 @@ class PortainerManager: def __init__(self): try: self.portainer = PortainerAPI() - self._set_portainer_token() + # self._set_portainer_token() except Exception as e: logger.error(f"Init Portainer API Error:{e}") raise CustomException() diff --git a/apphub/src/services/proxy_manager.py b/apphub/src/services/proxy_manager.py index 5843cec2..04886250 100755 --- a/apphub/src/services/proxy_manager.py +++ b/apphub/src/services/proxy_manager.py @@ -157,11 +157,11 @@ class ProxyManager: proxy_result = [] for proxy_host in proxys_host: if proxy_host.get("forward_host") == app_id: - proxy_data = { - "proxy_id": proxy_host.get("id"), - "domain_names": proxy_host.get("domain_names") - } - proxy_result.append(proxy_data) + # proxy_data = { + # "proxy_id": proxy_host.get("id"), + # "domain_names": proxy_host.get("domain_names") + # } + proxy_result.append(proxy_host) return proxy_result else: logger.error(f"Get proxy host by app:{app_id} error:{response.status_code}:{response.text}") @@ -171,9 +171,9 @@ class ProxyManager: proxy_hosts = self.get_proxy_host_by_app(app_id) if proxy_hosts: for proxy_host in proxy_hosts: - response = self.nginx.delete_proxy_host(proxy_host.get("proxy_id")) + response = self.nginx.delete_proxy_host(proxy_host.get("id")) if response.status_code != 200: - logger.error(f"Remove proxy host:{proxy_host.get('proxy_id')} for app:{app_id} error:{response.status_code}:{response.text}") + logger.error(f"Remove proxy host:{proxy_host.get('id')} for app:{app_id} error:{response.status_code}:{response.text}") raise CustomException() def remove_proxy_host_by_id(self,proxy_id:int):