Explorar o código

update apphub

zhaojing1987 hai 1 ano
pai
achega
63bb951b3f

+ 3 - 0
apphub/src/apphub.egg-info/PKG-INFO

@@ -0,0 +1,3 @@
+Metadata-Version: 2.1
+Name: apphub
+Version: 0.2

+ 10 - 0
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

+ 1 - 0
apphub/src/apphub.egg-info/dependency_links.txt

@@ -0,0 +1 @@
+

+ 2 - 0
apphub/src/apphub.egg-info/entry_points.txt

@@ -0,0 +1,2 @@
+[console_scripts]
+apphub = cli.apphub_cli:cli

+ 1 - 0
apphub/src/apphub.egg-info/requires.txt

@@ -0,0 +1 @@
+click

+ 1 - 0
apphub/src/apphub.egg-info/top_level.txt

@@ -0,0 +1 @@
+cli

+ 1 - 1
apphub/src/config/config.ini

@@ -22,7 +22,7 @@ path = /websoft9/library/apps
 path = /websoft9/media/json/
 path = /websoft9/media/json/
 
 
 [api_key]
 [api_key]
-key = c7c1c6876cbda1fb8f3a991f4893037e1d3f7a7da9e12a23b64c403e7054240f
+key = 9deeaf0d9f6f78c289f199a2631dc793b823d24024258385faaf833ea31e4ff6
 
 
 [domain]
 [domain]
 wildcard_domain = test.websoft9.cn
 wildcard_domain = test.websoft9.cn

+ 74 - 44
apphub/src/external/portainer_api.py

@@ -1,7 +1,46 @@
 import json
 import json
+import threading
 
 
 from src.core.apiHelper import APIHelper
 from src.core.apiHelper import APIHelper
 from src.core.config import ConfigManager
 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:
 class PortainerAPI:
@@ -29,6 +68,8 @@ class PortainerAPI:
         remove_volume_by_name(endpointId,volume_name): Remove volumes by name
         remove_volume_by_name(endpointId,volume_name): Remove volumes by name
     """
     """
 
 
+    jwt_token = None
+
     def __init__(self):
     def __init__(self):
         """
         """
         Initialize the PortainerAPI instance
         Initialize the PortainerAPI instance
@@ -37,38 +78,25 @@ class PortainerAPI:
             ConfigManager().get_value("portainer", "base_url"),
             ConfigManager().get_value("portainer", "base_url"),
             {
             {
                 "Content-Type": "application/json",
                 "Content-Type": "application/json",
+                # "Authorization": f"Bearer {JWTManager.get_token()}",
             },
             },
         )
         )
 
 
-    def set_jwt_token(self, jwt_token):
-        """
-        Set JWT token
-
-        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,
-            },
-        )
-       
+    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
+
+
+    @auto_refresh_token
     def get_endpoints(self,start: int = 0,limit: int = 1000):
     def get_endpoints(self,start: int = 0,limit: int = 1000):
         """
         """
         Get endpoints
         Get endpoints
@@ -84,6 +112,7 @@ class PortainerAPI:
             },
             },
         )
         )
     
     
+    @auto_refresh_token
     def get_endpoint_by_id(self, endpointId: int):
     def get_endpoint_by_id(self, endpointId: int):
         """
         """
         Get endpoint by ID
         Get endpoint by ID
@@ -96,6 +125,7 @@ class PortainerAPI:
         """
         """
         return self.api.get(path=f"endpoints/{endpointId}")
         return self.api.get(path=f"endpoints/{endpointId}")
 
 
+    @auto_refresh_token
     def create_endpoint(self, name: str, EndpointCreationType: int = 1):
     def create_endpoint(self, name: str, EndpointCreationType: int = 1):
         """
         """
         Create an endpoint
         Create an endpoint
@@ -113,6 +143,7 @@ class PortainerAPI:
             params={"Name": name, "EndpointCreationType": EndpointCreationType},
             params={"Name": name, "EndpointCreationType": EndpointCreationType},
         )
         )
 
 
+    @auto_refresh_token
     def get_stacks(self, endpointId: int):
     def get_stacks(self, endpointId: int):
         """
         """
         Get stacks
         Get stacks
@@ -132,6 +163,7 @@ class PortainerAPI:
             },
             },
         )
         )
 
 
+    @auto_refresh_token
     def get_stack_by_id(self, stackID: int):
     def get_stack_by_id(self, stackID: int):
         """
         """
         Get stack by ID
         Get stack by ID
@@ -144,6 +176,7 @@ class PortainerAPI:
         """
         """
         return self.api.get(path=f"stacks/{stackID}")
         return self.api.get(path=f"stacks/{stackID}")
 
 
+    @auto_refresh_token
     def remove_stack(self, stackID: int, endpointId: int):
     def remove_stack(self, stackID: int, endpointId: int):
         """
         """
         Remove a stack
         Remove a stack
@@ -159,6 +192,7 @@ class PortainerAPI:
             path=f"stacks/{stackID}", params={"endpointId": endpointId}
             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):
     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
         Create a stack from a standalone repository
@@ -184,6 +218,7 @@ class PortainerAPI:
             },
             },
         )
         )
 
 
+    @auto_refresh_token
     def up_stack(self, stackID: int, endpointId: int):
     def up_stack(self, stackID: int, endpointId: int):
         """
         """
         Up a stack
         Up a stack
@@ -199,6 +234,7 @@ class PortainerAPI:
             path=f"stacks/{stackID}/start", params={"endpointId": endpointId}
             path=f"stacks/{stackID}/start", params={"endpointId": endpointId}
         )
         )
 
 
+    @auto_refresh_token
     def down_stack(self, stackID: int, endpointId: int):
     def down_stack(self, stackID: int, endpointId: int):
         """
         """
         Down a stack
         Down a stack
@@ -214,21 +250,7 @@ class PortainerAPI:
             path=f"stacks/{stackID}/stop", params={"endpointId": endpointId}
             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):
     def get_volumes(self, endpointId: int,dangling: bool):
         """
         """
         Get volumes in endpoint
         Get volumes in endpoint
@@ -246,6 +268,7 @@ class PortainerAPI:
         }
         }
     )
     )
     
     
+    @auto_refresh_token
     def remove_volume_by_name(self, endpointId: int,volume_name:str):
     def remove_volume_by_name(self, endpointId: int,volume_name:str):
         """
         """
         Remove volumes by name
         Remove volumes by name
@@ -258,6 +281,7 @@ class PortainerAPI:
         path=f"endpoints/{endpointId}/docker/volumes/{volume_name}",
         path=f"endpoints/{endpointId}/docker/volumes/{volume_name}",
     )
     )
 
 
+    @auto_refresh_token
     def get_containers(self, endpointId: int):
     def get_containers(self, endpointId: int):
         """
         """
         Get containers in endpoint
         Get containers in endpoint
@@ -272,6 +296,7 @@ class PortainerAPI:
             }
             }
         )
         )
 
 
+    @auto_refresh_token
     def get_containers_by_stackName(self, endpointId: int,stack_name:str):
     def get_containers_by_stackName(self, endpointId: int,stack_name:str):
         """
         """
         Get containers in endpoint
         Get containers in endpoint
@@ -289,6 +314,7 @@ class PortainerAPI:
             }
             }
         )
         )
 
 
+    @auto_refresh_token
     def get_container_by_id(self, endpointId: int, container_id: str):
     def get_container_by_id(self, endpointId: int, container_id: str):
         """
         """
         Get container by ID
         Get container by ID
@@ -301,6 +327,7 @@ class PortainerAPI:
             path=f"endpoints/{endpointId}/docker/containers/{container_id}/json",
             path=f"endpoints/{endpointId}/docker/containers/{container_id}/json",
         )
         )
 
 
+    @auto_refresh_token
     def stop_container(self, endpointId: int, container_id: str):
     def stop_container(self, endpointId: int, container_id: str):
         """
         """
         Stop container
         Stop container
@@ -313,6 +340,7 @@ class PortainerAPI:
             path=f"endpoints/{endpointId}/docker/containers/{container_id}/stop",
             path=f"endpoints/{endpointId}/docker/containers/{container_id}/stop",
         )
         )
     
     
+    @auto_refresh_token
     def start_container(self, endpointId: int, container_id: str):
     def start_container(self, endpointId: int, container_id: str):
         """
         """
         Start container
         Start container
@@ -325,6 +353,7 @@ class PortainerAPI:
             path=f"endpoints/{endpointId}/docker/containers/{container_id}/start",
             path=f"endpoints/{endpointId}/docker/containers/{container_id}/start",
         )
         )
     
     
+    @auto_refresh_token
     def restart_container(self, endpointId: int, container_id: str):
     def restart_container(self, endpointId: int, container_id: str):
         """
         """
         Restart container
         Restart container
@@ -337,6 +366,7 @@ class PortainerAPI:
             path=f"endpoints/{endpointId}/docker/containers/{container_id}/restart",
             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 ):
     def redeploy_stack(self, stackID: int, endpointId: int,pullImage:bool,user_name:str,user_password:str ):
         return self.api.put(
         return self.api.put(
             path=f"stacks/{stackID}/git/redeploy", 
             path=f"stacks/{stackID}/git/redeploy", 

+ 2 - 2
apphub/src/main.py

@@ -25,10 +25,9 @@ stdout_handler = logging.StreamHandler(sys.stdout)
 
 
 # 将日志处理器添加到 Uvicorn 的 logger
 # 将日志处理器添加到 Uvicorn 的 logger
 uvicorn_logger.addHandler(stdout_handler)
 uvicorn_logger.addHandler(stdout_handler)
-
 uvicorn_logger.setLevel(logging.INFO)
 uvicorn_logger.setLevel(logging.INFO)
 
 
-API_KEY = ConfigManager().get_value("api_key","key")
+
 API_KEY_NAME = "api_key"
 API_KEY_NAME = "api_key"
 api_key_header = APIKeyHeader(name=API_KEY_NAME, auto_error=False)
 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",
             message="Invalid Request",
             details="No API Key provided"
             details="No API Key provided"
         )
         )
+    API_KEY = ConfigManager().get_value("api_key","key")
 
 
     if api_key_header != API_KEY:
     if api_key_header != API_KEY:
         logger.error(f"Invalid API Key: {api_key_header}")
         logger.error(f"Invalid API Key: {api_key_header}")

+ 7 - 6
apphub/src/schemas/appResponse.py

@@ -1,20 +1,21 @@
 from pydantic import BaseModel
 from pydantic import BaseModel
 from typing import List, Any
 from typing import List, Any
 from pydantic import BaseModel, Field
 from pydantic import BaseModel, Field
+from typing import Optional
 
 
 class AppResponse(BaseModel):
 class AppResponse(BaseModel):
     app_id: str=Field("", description="App ID",example="wordpress")
     app_id: str=Field("", description="App ID",example="wordpress")
     endpointId: int=Field(-1, description="Endpoint ID(-1:Not install on app store)",example=1)
     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)              
     app_official: bool=Field(True, description="App official",example=True)              
     proxy_enabled: bool=Field(False, description="Proxy enabled",example=False)
     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)
     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")
     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")
     gitConfig: dict[str, Any] = Field({}, description="Git configuration")
     containers: List[dict]  = Field([], description="Containers")
     containers: List[dict]  = Field([], description="Containers")
     volumes: List[dict] = Field([], description="Volumes")
     volumes: List[dict] = Field([], description="Volumes")

+ 13 - 10
apphub/src/services/app_manager.py

@@ -21,7 +21,6 @@ from src.utils.password_generator import PasswordGenerator
 
 
 class AppManger:
 class AppManger:
     def get_catalog_apps(self,locale:str):
     def get_catalog_apps(self,locale:str):
-        logger.access(f"Get catalog apps: {locale}")
         try:
         try:
             # Get the app media path
             # Get the app media path
             base_path = ConfigManager().get_value("app_media", "path")
             base_path = ConfigManager().get_value("app_media", "path")
@@ -153,6 +152,8 @@ class AppManger:
             
             
             # Get the main container
             # Get the main container
             main_container_id = None
             main_container_id = None
+            app_env = []
+            app_env_format = {} # format app_env to dict
             for container in app_containers:
             for container in app_containers:
                 if f"/{app_id}" in container.get("Names", []):
                 if f"/{app_id}" in container.get("Names", []):
                     main_container_id = container.get("Id", "")
                     main_container_id = container.get("Id", "")
@@ -160,15 +161,17 @@ class AppManger:
             if main_container_id:
             if main_container_id:
                 # Get the main container info
                 # Get the main container info
                 main_container_info =  portainerManager.get_container_by_id(endpointId, main_container_id)
                 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
             # Get http port from env
             app_http_port = None
             app_http_port = None
             app_name = None
             app_name = None
             app_dist = None
             app_dist = None
+            app_version = None
             for item in app_env:
             for item in app_env:
                 key, value = item.split("=", 1)
                 key, value = item.split("=", 1)
+                app_env_format[key] = value
                 if key == "APP_HTTP_PORT":
                 if key == "APP_HTTP_PORT":
                     app_http_port = value
                     app_http_port = value
                 elif key == "APP_NAME":
                 elif key == "APP_NAME":
@@ -205,7 +208,7 @@ class AppManger:
                 gitConfig = gitConfig,
                 gitConfig = gitConfig,
                 containers = app_containers,
                 containers = app_containers,
                 volumes = app_volumes,
                 volumes = app_volumes,
-                env = app_env
+                env = app_env_format
             )
             )
             return appResponse
             return appResponse
         else:
         else:
@@ -224,7 +227,7 @@ class AppManger:
                 gitConfig = gitConfig,
                 gitConfig = gitConfig,
                 containers = [],
                 containers = [],
                 volumes = app_volumes,
                 volumes = app_volumes,
-                env = []
+                env = {}
             )
             )
             return appResponse
             return appResponse
     
     
@@ -392,7 +395,7 @@ class AppManger:
             raise CustomException(
             raise CustomException(
                 status_code=400,
                 status_code=400,
                 message="Invalid Request",
                 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:
         if purge_data:
@@ -454,7 +457,7 @@ class AppManger:
             raise CustomException(
             raise CustomException(
                 status_code=400,
                 status_code=400,
                 message="Invalid Request",
                 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
         # Check the proxy is exists
         proxyManager = ProxyManager()
         proxyManager = ProxyManager()
@@ -497,7 +500,7 @@ class AppManger:
             raise CustomException(
             raise CustomException(
                 status_code=400,
                 status_code=400,
                 message="Invalid Request",
                 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)
         portainerManager.start_stack(app_id,endpointId)
@@ -528,7 +531,7 @@ class AppManger:
             raise CustomException(
             raise CustomException(
                 status_code=400,
                 status_code=400,
                 message="Invalid Request",
                 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)
         portainerManager.stop_stack(app_id,endpointId)
 
 
@@ -558,7 +561,7 @@ class AppManger:
             raise CustomException(
             raise CustomException(
                 status_code=400,
                 status_code=400,
                 message="Invalid Request",
                 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)
         portainerManager.restart_stack(app_id,endpointId)
         
         

+ 1 - 1
apphub/src/services/portainer_manager.py

@@ -32,7 +32,7 @@ class PortainerManager:
     def __init__(self):
     def __init__(self):
         try:
         try:
             self.portainer = PortainerAPI()
             self.portainer = PortainerAPI()
-            self._set_portainer_token()
+            # self._set_portainer_token()
         except Exception as e:
         except Exception as e:
             logger.error(f"Init Portainer API Error:{e}")
             logger.error(f"Init Portainer API Error:{e}")
             raise CustomException()
             raise CustomException()

+ 7 - 7
apphub/src/services/proxy_manager.py

@@ -157,11 +157,11 @@ class ProxyManager:
             proxy_result = []
             proxy_result = []
             for proxy_host in proxys_host:
             for proxy_host in proxys_host:
                 if proxy_host.get("forward_host") == app_id:
                 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
             return proxy_result
         else:
         else:
             logger.error(f"Get proxy host by app:{app_id} error:{response.status_code}:{response.text}")
             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)
         proxy_hosts = self.get_proxy_host_by_app(app_id)
         if proxy_hosts:
         if proxy_hosts:
             for proxy_host in 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:
                 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()
                     raise CustomException()
 
 
     def remove_proxy_host_by_id(self,proxy_id:int):
     def remove_proxy_host_by_id(self,proxy_id:int):