mirror of
https://github.com/Websoft9/websoft9.git
synced 2024-11-22 07:30:24 +00:00
update apphub
This commit is contained in:
parent
2af583dc3d
commit
63bb951b3f
13 changed files with 121 additions and 69 deletions
3
apphub/src/apphub.egg-info/PKG-INFO
Normal file
3
apphub/src/apphub.egg-info/PKG-INFO
Normal file
|
@ -0,0 +1,3 @@
|
|||
Metadata-Version: 2.1
|
||||
Name: apphub
|
||||
Version: 0.2
|
10
apphub/src/apphub.egg-info/SOURCES.txt
Normal file
10
apphub/src/apphub.egg-info/SOURCES.txt
Normal 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
|
1
apphub/src/apphub.egg-info/dependency_links.txt
Normal file
1
apphub/src/apphub.egg-info/dependency_links.txt
Normal file
|
@ -0,0 +1 @@
|
|||
|
2
apphub/src/apphub.egg-info/entry_points.txt
Normal file
2
apphub/src/apphub.egg-info/entry_points.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
[console_scripts]
|
||||
apphub = cli.apphub_cli:cli
|
1
apphub/src/apphub.egg-info/requires.txt
Normal file
1
apphub/src/apphub.egg-info/requires.txt
Normal file
|
@ -0,0 +1 @@
|
|||
click
|
1
apphub/src/apphub.egg-info/top_level.txt
Normal file
1
apphub/src/apphub.egg-info/top_level.txt
Normal file
|
@ -0,0 +1 @@
|
|||
cli
|
|
@ -22,7 +22,7 @@ path = /websoft9/library/apps
|
|||
path = /websoft9/media/json/
|
||||
|
||||
[api_key]
|
||||
key = c7c1c6876cbda1fb8f3a991f4893037e1d3f7a7da9e12a23b64c403e7054240f
|
||||
key = 9deeaf0d9f6f78c289f199a2631dc793b823d24024258385faaf833ea31e4ff6
|
||||
|
||||
[domain]
|
||||
wildcard_domain = test.websoft9.cn
|
||||
|
|
114
apphub/src/external/portainer_api.py
vendored
114
apphub/src/external/portainer_api.py
vendored
|
@ -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",
|
||||
|
|
|
@ -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}")
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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):
|
||||
|
|
Loading…
Reference in a new issue