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/
[api_key]
key = c7c1c6876cbda1fb8f3a991f4893037e1d3f7a7da9e12a23b64c403e7054240f
key = 9deeaf0d9f6f78c289f199a2631dc793b823d24024258385faaf833ea31e4ff6
[domain]
wildcard_domain = test.websoft9.cn

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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