update apphub

This commit is contained in:
zhaojing1987 2023-11-02 08:55:19 +08:00
parent 2af583dc3d
commit 63bb951b3f
13 changed files with 121 additions and 69 deletions

View file

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

View file

@ -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

View file

@ -0,0 +1 @@

View file

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

View file

@ -0,0 +1 @@
click

View file

@ -0,0 +1 @@
cli

View file

@ -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

View file

@ -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): def auto_refresh_token(func):
""" @wraps(func)
Set JWT token 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): 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): @auto_refresh_token
"""
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}
)
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",

View file

@ -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}")

View file

@ -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_name: Optional[str]=Field(None, description="App name",example="wordpress")
app_port: int=Field(0, description="App port",example=80) app_port: Optional[int] = Field(None, description="App port", example=80)
app_dist: str=Field("", description="App dist",example="community") app_dist: Optional[str]=Field(None, description="App dist",example="community")
app_version: str=Field("", description="App version",example="1.0.0") 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")

View file

@ -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", "")
@ -167,8 +168,10 @@ class AppManger:
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)

View file

@ -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()

View file

@ -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_data = {
"proxy_id": proxy_host.get("id"), # "proxy_id": proxy_host.get("id"),
"domain_names": proxy_host.get("domain_names") # "domain_names": proxy_host.get("domain_names")
} # }
proxy_result.append(proxy_data) 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):