mirror of
https://github.com/Websoft9/websoft9.git
synced 2024-11-25 09:00:26 +00:00
update
This commit is contained in:
parent
f7b3079fc5
commit
177cd076c2
13 changed files with 1194 additions and 155 deletions
|
@ -1,4 +1,4 @@
|
||||||
# run app : uvicorn src.main:app --reload --port 9999
|
# run app : uvicorn src.main:app --reload --port 9999 --log-level error
|
||||||
# run nginx proxy manager doc:docker run -p 9091:8080 -e SWAGGER_JSON=/foo/api.swagger.json -v /data/websoft9/appmanage_new/docs/:/foo swaggerapi/swagger-ui
|
# run nginx proxy manager doc:docker run -p 9091:8080 -e SWAGGER_JSON=/foo/api.swagger.json -v /data/websoft9/appmanage_new/docs/:/foo swaggerapi/swagger-ui
|
||||||
# supervisorctl
|
# supervisorctl
|
||||||
## supervisorctl reload
|
## supervisorctl reload
|
||||||
|
|
|
@ -1,15 +1,72 @@
|
||||||
from fastapi import APIRouter, Query
|
from fastapi import APIRouter, Query,Path
|
||||||
|
from src.schemas.appAvailable import AppAvailableResponse
|
||||||
|
from src.schemas.appCatalog import AppCatalogResponse
|
||||||
from src.schemas.appInstall import appInstall
|
from src.schemas.appInstall import appInstall
|
||||||
|
from src.schemas.appResponse import AppResponse
|
||||||
from src.schemas.errorResponse import ErrorResponse
|
from src.schemas.errorResponse import ErrorResponse
|
||||||
from src.services.app_manager import AppManger
|
from src.services.app_manager import AppManger
|
||||||
|
|
||||||
router = APIRouter(prefix="/api/v1")
|
router = APIRouter(prefix="/api/v1")
|
||||||
|
|
||||||
|
@router.get(
|
||||||
|
"/apps/catalog/{locale}",
|
||||||
|
summary="List Catalogs",
|
||||||
|
description="List all app's catalogs",
|
||||||
|
responses={
|
||||||
|
200: {"model": list[AppCatalogResponse]},
|
||||||
|
400: {"model": ErrorResponse},
|
||||||
|
500: {"model": ErrorResponse},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
def get_catalog_apps(
|
||||||
|
locale: str = Path(..., description="Language to get catalogs from", regex="^(zh|en)$"),
|
||||||
|
):
|
||||||
|
return AppManger().get_catalog_apps(locale)
|
||||||
|
|
||||||
@router.get("/apps/")
|
|
||||||
|
@router.get(
|
||||||
|
"/apps/available/{locale}",
|
||||||
|
summary="List Available Apps",
|
||||||
|
description="List all available apps",
|
||||||
|
responses={
|
||||||
|
200: {"model": list[AppAvailableResponse]},
|
||||||
|
400: {"model": ErrorResponse},
|
||||||
|
500: {"model": ErrorResponse},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
def get_available_apps(
|
||||||
|
locale: str = Path(..., description="Language to get available apps from", regex="^(zh|en)$"),
|
||||||
|
):
|
||||||
|
return AppManger().get_available_apps(locale)
|
||||||
|
|
||||||
|
@router.get(
|
||||||
|
"/apps",
|
||||||
|
summary="List Installed Apps",
|
||||||
|
description="List all installed apps",
|
||||||
|
responses={
|
||||||
|
200: {"model": list[AppResponse]},
|
||||||
|
400: {"model": ErrorResponse},
|
||||||
|
500: {"model": ErrorResponse},
|
||||||
|
}
|
||||||
|
)
|
||||||
def get_apps():
|
def get_apps():
|
||||||
return {"apps": []}
|
return AppManger().get_apps()
|
||||||
|
|
||||||
|
@router.get(
|
||||||
|
"/apps/{app_id}",
|
||||||
|
summary="Inspect App",
|
||||||
|
description="Retrieve details about an app",
|
||||||
|
responses={
|
||||||
|
200: {"model": AppResponse},
|
||||||
|
400: {"model": ErrorResponse},
|
||||||
|
500: {"model": ErrorResponse},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
def get_app_by_id(
|
||||||
|
app_id: str = Path(..., description="App ID to get details from"),
|
||||||
|
endpointId: int = Query(None, description="Endpoint ID to get app details from. If not set, get details from the local endpoint")
|
||||||
|
):
|
||||||
|
return AppManger().get_app_by_id(app_id, endpointId)
|
||||||
|
|
||||||
@router.post(
|
@router.post(
|
||||||
"/apps/install",
|
"/apps/install",
|
||||||
|
@ -26,4 +83,116 @@ def apps_install(
|
||||||
appInstall: appInstall,
|
appInstall: appInstall,
|
||||||
endpointId: int = Query(None, description="Endpoint ID to install app on,if not set, install on the local endpoint"),
|
endpointId: int = Query(None, description="Endpoint ID to install app on,if not set, install on the local endpoint"),
|
||||||
):
|
):
|
||||||
AppManger().install_app(appInstall, endpointId)
|
return AppManger().install_app(appInstall, endpointId)
|
||||||
|
|
||||||
|
|
||||||
|
@router.post(
|
||||||
|
"/apps/{app_id}/start",
|
||||||
|
summary="Start App",
|
||||||
|
description="Start an app on an endpoint",
|
||||||
|
status_code=204,
|
||||||
|
responses={
|
||||||
|
204: {"description": "App started successfully"},
|
||||||
|
400: {"model": ErrorResponse},
|
||||||
|
500: {"model": ErrorResponse},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
def app_start(
|
||||||
|
app_id: str = Path(..., description="App ID to start"),
|
||||||
|
endpointId: int = Query(None, description="Endpoint ID to start app on. If not set, start on the local endpoint")
|
||||||
|
):
|
||||||
|
AppManger().start_app(app_id, endpointId)
|
||||||
|
|
||||||
|
|
||||||
|
@router.post(
|
||||||
|
"/apps/{app_id}/stop",
|
||||||
|
summary="Stop App",
|
||||||
|
response_model_exclude_defaults=True,
|
||||||
|
description="Stop an app on an endpoint",
|
||||||
|
status_code=204,
|
||||||
|
responses={
|
||||||
|
204: {"description": "App stopped successfully"},
|
||||||
|
400: {"model": ErrorResponse},
|
||||||
|
500: {"model": ErrorResponse},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
def app_stop(
|
||||||
|
app_id: str = Path(..., description="App ID to stop"),
|
||||||
|
endpointId: int = Query(None, description="Endpoint ID to stop app on. If not set, stop on the local endpoint"),
|
||||||
|
):
|
||||||
|
AppManger().stop_app(app_id, endpointId)
|
||||||
|
|
||||||
|
@router.post(
|
||||||
|
"/apps/{app_id}/restart",
|
||||||
|
summary="Restart App",
|
||||||
|
response_model_exclude_defaults=True,
|
||||||
|
description="Restart an app on an endpoint",
|
||||||
|
status_code=204,
|
||||||
|
responses={
|
||||||
|
204: {"description": "App restarted successfully"},
|
||||||
|
400: {"model": ErrorResponse},
|
||||||
|
500: {"model": ErrorResponse},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
def app_restart(
|
||||||
|
app_id: str = Path(..., description="App ID to restart"),
|
||||||
|
endpointId: int = Query(None, description="Endpoint ID to Restart app on. If not set, Restart on the local endpoint"),
|
||||||
|
):
|
||||||
|
AppManger().restart_app(app_id, endpointId)
|
||||||
|
|
||||||
|
@router.put(
|
||||||
|
"/apps/{app_id}/redeploy",
|
||||||
|
summary="Redeploy App",
|
||||||
|
response_model_exclude_defaults=True,
|
||||||
|
description="Redeploy an app on an endpoint",
|
||||||
|
status_code=204,
|
||||||
|
responses={
|
||||||
|
204: {"description": "App redeploy successfully"},
|
||||||
|
400: {"model": ErrorResponse},
|
||||||
|
500: {"model": ErrorResponse},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
def app_redeploy(
|
||||||
|
app_id: str = Path(..., description="App ID to redeploy"),
|
||||||
|
endpointId: int = Query(None, description="Endpoint ID to redeploy app on. If not set, redeploy on the local endpoint"),
|
||||||
|
pullImage: bool = Query(..., description="Whether to pull the image when redeploying the app"),
|
||||||
|
):
|
||||||
|
AppManger().redeploy_app(app_id, pullImage,endpointId)
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete(
|
||||||
|
"/apps/{app_id}/uninstall",
|
||||||
|
summary="Uninstall App",
|
||||||
|
description="Uninstall an app on an endpoint",
|
||||||
|
status_code=204,
|
||||||
|
responses={
|
||||||
|
204: {"description": "App uninstalled successfully"},
|
||||||
|
400: {"model": ErrorResponse},
|
||||||
|
500: {"model": ErrorResponse},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
def apps_uninstall(
|
||||||
|
app_id: str=Path(..., description="App ID to uninstall"),
|
||||||
|
endpointId: int = Query(None, description="Endpoint ID to uninstall app on,if not set, uninstall on the local endpoint"),
|
||||||
|
purge_data: bool = Query(..., description="Whether to purge data when uninstalling the app")
|
||||||
|
):
|
||||||
|
AppManger().uninstall_app(app_id,purge_data, endpointId)
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete(
|
||||||
|
"/apps/{app_id}/remove",
|
||||||
|
summary="Remove App",
|
||||||
|
response_model_exclude_defaults=True,
|
||||||
|
description="Remove an app on an endpoint where the app is empty(status is 'inactive')",
|
||||||
|
status_code=204,
|
||||||
|
responses={
|
||||||
|
204: {"description": "App removed successfully"},
|
||||||
|
400: {"model": ErrorResponse},
|
||||||
|
500: {"model": ErrorResponse},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
def app_remove(
|
||||||
|
app_id: str = Path(..., description="App ID to remove"),
|
||||||
|
endpointId: int = Query(None, description="Endpoint ID to remove app on. If not set, remove on the local endpoint"),
|
||||||
|
):
|
||||||
|
AppManger().remove_app(app_id, endpointId)
|
||||||
|
|
|
@ -7,26 +7,29 @@ access_token =
|
||||||
base_url = http://websoft9-proxy:81/api
|
base_url = http://websoft9-proxy:81/api
|
||||||
#base_url = http://47.92.222.186/w9proxy/api
|
#base_url = http://47.92.222.186/w9proxy/api
|
||||||
user_name = help@websoft9.com
|
user_name = help@websoft9.com
|
||||||
user_pwd = websoft9@2023
|
user_pwd = ECTKPRAWhij789yr
|
||||||
|
|
||||||
#The config for gitea
|
#The config for gitea
|
||||||
[gitea]
|
[gitea]
|
||||||
base_url = http://websoft9-git:3000/api/v1
|
base_url = http://websoft9-git:3000/api/v1
|
||||||
# base_url = http://47.92.222.186/w9git/api/v1
|
# base_url = http://47.92.222.186/w9git/api/v1
|
||||||
user_name = websoft9
|
user_name = websoft9
|
||||||
user_pwd = O4rXXHkSoKVY
|
user_pwd = Rk9qOQ68Inf0
|
||||||
|
|
||||||
#The config for portainer
|
#The config for portainer
|
||||||
[portainer]
|
[portainer]
|
||||||
base_url = http://websoft9-deployment:9000/api
|
base_url = http://websoft9-deployment:9000/api
|
||||||
#base_url = http://47.92.222.186/w9deployment/api
|
#base_url = http://47.92.222.186/w9deployment/api
|
||||||
user_name = admin
|
user_name = admin
|
||||||
user_pwd = &uswVF^wMyi]wpdc
|
user_pwd = ]}fU;XmVH].VI{Hh
|
||||||
|
|
||||||
#The path of docker library
|
#The path of docker library
|
||||||
[docker_library]
|
[docker_library]
|
||||||
path = /websoft9/library/apps
|
path = /websoft9/library/apps
|
||||||
|
|
||||||
|
[app_media]
|
||||||
|
path = /websoft9/media/json/
|
||||||
|
|
||||||
# public_ip_url_list is a list of public ip url, which is used to get the public ip of the server
|
# public_ip_url_list is a list of public ip url, which is used to get the public ip of the server
|
||||||
[public_ip_url_list]
|
[public_ip_url_list]
|
||||||
url_list = https://api.ipify.org/,
|
url_list = https://api.ipify.org/,
|
||||||
|
|
|
@ -13,11 +13,12 @@ class NginxProxyManagerAPI:
|
||||||
api (APIHelper): API helper
|
api (APIHelper): API helper
|
||||||
|
|
||||||
Methods:
|
Methods:
|
||||||
get_token(identity: str, secret: str) -> Response: Request a new access token
|
set_token(api_token): Set API token
|
||||||
get_proxy_hosts() -> Response: Get all proxy hosts
|
get_token(identity, secret): Request a new access token
|
||||||
create_proxy_host(domain_names: List[str], forward_scheme: str, forward_host: str, forward_port: int, advanced_config: str) -> Response: Create a new proxy host
|
get_proxy_hosts(): Get all proxy hosts
|
||||||
update_proxy_host(proxy_id: int, domain_names: List[str], forward_scheme: str, forward_host: str, forward_port: int, advanced_config: str) -> Response: Update an existing proxy host
|
create_proxy_host(domain_names, forward_scheme, forward_host, forward_port, advanced_config): Create a new proxy host
|
||||||
delete_proxy_host(proxy_id: int) -> Response: Delete a proxy host
|
update_proxy_host(proxy_id, domain_names, forward_scheme, forward_host, forward_port, advanced_config): Update an existing proxy host
|
||||||
|
delete_proxy_host(proxy_id): Delete a proxy host
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
130
appmanage_new/src/external/portainer_api.py
vendored
130
appmanage_new/src/external/portainer_api.py
vendored
|
@ -13,15 +13,20 @@ class PortainerAPI:
|
||||||
api (APIHelper): API helper
|
api (APIHelper): API helper
|
||||||
|
|
||||||
Methods:
|
Methods:
|
||||||
get_jwt_token(username: str, password: str) -> Response): Get JWT token
|
set_jwt_token(jwt_token): Set JWT token
|
||||||
get_endpoints() -> Response: Get endpoints
|
get_jwt_token(username, password): Get JWT token
|
||||||
get_stacks(endpointID: int) -> Response: Get stacks
|
get_endpoints(): Get endpoints
|
||||||
get_stack_by_id(stackID: int) -> Response: Get stack by ID
|
get_endpoint_by_id(endpointId): Get endpoint by ID
|
||||||
remove_stack(stackID: int,endPointID: int) -> Response: Remove a stack
|
create_endpoint(name, EndpointCreationType): Create an endpoint
|
||||||
create_stack_standlone_repository(app_name: str, endpointId: int,repositoryURL:str) -> Response: Create a stack from a standalone repository
|
get_stacks(endpointId): Get stacks
|
||||||
start_stack(stackID: int, endpointId: int) -> Response: Start a stack
|
get_stack_by_id(stackID): Get stack by ID
|
||||||
stop_stack(stackID: int, endpointId: int) -> Response: Stop a stack
|
remove_stack(stackID, endpointId): Remove a stack
|
||||||
redeploy_stack(stackID: int, endpointId: int) -> Response: Redeploy a stack
|
create_stack_standlone_repository(stack_name, endpointId, repositoryURL): Create a stack from a standalone repository
|
||||||
|
start_stack(stackID, endpointId): Start a stack
|
||||||
|
stop_stack(stackID, endpointId): Stop a stack
|
||||||
|
redeploy_stack(stackID, endpointId): Redeploy a stack
|
||||||
|
get_volumes(endpointId,dangling): Get volumes in endpoint
|
||||||
|
remove_volume_by_name(endpointId,volume_name): Remove volumes by name
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
@ -179,9 +184,9 @@ class PortainerAPI:
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
def start_stack(self, stackID: int, endpointId: int):
|
def up_stack(self, stackID: int, endpointId: int):
|
||||||
"""
|
"""
|
||||||
Start a stack
|
Up a stack
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
stackID (int): Stack ID
|
stackID (int): Stack ID
|
||||||
|
@ -194,9 +199,9 @@ class PortainerAPI:
|
||||||
path=f"stacks/{stackID}/start", params={"endpointId": endpointId}
|
path=f"stacks/{stackID}/start", params={"endpointId": endpointId}
|
||||||
)
|
)
|
||||||
|
|
||||||
def stop_stack(self, stackID: int, endpointId: int):
|
def down_stack(self, stackID: int, endpointId: int):
|
||||||
"""
|
"""
|
||||||
Stop a stack
|
Down a stack
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
stackID (int): Stack ID
|
stackID (int): Stack ID
|
||||||
|
@ -224,12 +229,13 @@ class PortainerAPI:
|
||||||
path=f"stacks/{stackID}/redeploy", params={"endpointId": endpointId}
|
path=f"stacks/{stackID}/redeploy", params={"endpointId": endpointId}
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_volumes(self, endpointId: int,dangling: bool = False):
|
def get_volumes(self, endpointId: int,dangling: bool):
|
||||||
"""
|
"""
|
||||||
Get volumes in endpoint
|
Get volumes in endpoint
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
endpointId (int): Endpoint ID
|
endpointId (int): Endpoint ID
|
||||||
|
dangling (bool): the volume is dangling or not
|
||||||
"""
|
"""
|
||||||
return self.api.get(
|
return self.api.get(
|
||||||
path=f"endpoints/{endpointId}/docker/volumes",
|
path=f"endpoints/{endpointId}/docker/volumes",
|
||||||
|
@ -250,4 +256,98 @@ class PortainerAPI:
|
||||||
"""
|
"""
|
||||||
return self.api.delete(
|
return self.api.delete(
|
||||||
path=f"endpoints/{endpointId}/docker/volumes/{volume_name}",
|
path=f"endpoints/{endpointId}/docker/volumes/{volume_name}",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def get_containers(self, endpointId: int):
|
||||||
|
"""
|
||||||
|
Get containers in endpoint
|
||||||
|
|
||||||
|
Args:
|
||||||
|
endpointId (int): Endpoint ID
|
||||||
|
"""
|
||||||
|
return self.api.get(
|
||||||
|
path=f"endpoints/{endpointId}/docker/containers/json",
|
||||||
|
params={
|
||||||
|
"all": True,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_containers_by_stackName(self, endpointId: int,stack_name:str):
|
||||||
|
"""
|
||||||
|
Get containers in endpoint
|
||||||
|
|
||||||
|
Args:
|
||||||
|
endpointId (int): Endpoint ID
|
||||||
|
"""
|
||||||
|
return self.api.get(
|
||||||
|
path=f"endpoints/{endpointId}/docker/containers/json",
|
||||||
|
params={
|
||||||
|
"all": True,
|
||||||
|
"filters": json.dumps(
|
||||||
|
{"label": [f"com.docker.compose.project={stack_name}"]}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_container_by_id(self, endpointId: int, container_id: str):
|
||||||
|
"""
|
||||||
|
Get container by ID
|
||||||
|
|
||||||
|
Args:
|
||||||
|
endpointId (int): Endpoint ID
|
||||||
|
container_id (str): container ID
|
||||||
|
"""
|
||||||
|
return self.api.get(
|
||||||
|
path=f"endpoints/{endpointId}/docker/containers/{container_id}/json",
|
||||||
|
)
|
||||||
|
|
||||||
|
def stop_container(self, endpointId: int, container_id: str):
|
||||||
|
"""
|
||||||
|
Stop container
|
||||||
|
|
||||||
|
Args:
|
||||||
|
endpointId (int): Endpoint ID
|
||||||
|
container_id (str): container ID
|
||||||
|
"""
|
||||||
|
return self.api.post(
|
||||||
|
path=f"endpoints/{endpointId}/docker/containers/{container_id}/stop",
|
||||||
|
)
|
||||||
|
|
||||||
|
def start_container(self, endpointId: int, container_id: str):
|
||||||
|
"""
|
||||||
|
Start container
|
||||||
|
|
||||||
|
Args:
|
||||||
|
endpointId (int): Endpoint ID
|
||||||
|
container_id (str): container ID
|
||||||
|
"""
|
||||||
|
return self.api.post(
|
||||||
|
path=f"endpoints/{endpointId}/docker/containers/{container_id}/start",
|
||||||
|
)
|
||||||
|
|
||||||
|
def restart_container(self, endpointId: int, container_id: str):
|
||||||
|
"""
|
||||||
|
Restart container
|
||||||
|
|
||||||
|
Args:
|
||||||
|
endpointId (int): Endpoint ID
|
||||||
|
container_id (str): container ID
|
||||||
|
"""
|
||||||
|
return self.api.post(
|
||||||
|
path=f"endpoints/{endpointId}/docker/containers/{container_id}/restart",
|
||||||
|
)
|
||||||
|
|
||||||
|
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",
|
||||||
|
params={"endpointId": endpointId},
|
||||||
|
json={
|
||||||
|
"env":[],
|
||||||
|
"prune":False,
|
||||||
|
"RepositoryReferenceName":"",
|
||||||
|
"RepositoryAuthentication":True,
|
||||||
|
"RepositoryUsername":user_name,
|
||||||
|
"RepositoryPassword":user_password,
|
||||||
|
"PullImage":pullImage
|
||||||
|
}
|
||||||
|
)
|
|
@ -1,6 +1,4 @@
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import uvicorn
|
|
||||||
from fastapi import FastAPI, Request
|
from fastapi import FastAPI, Request
|
||||||
from fastapi.exceptions import RequestValidationError
|
from fastapi.exceptions import RequestValidationError
|
||||||
from fastapi.responses import JSONResponse
|
from fastapi.responses import JSONResponse
|
||||||
|
@ -11,12 +9,11 @@ from src.core.exception import CustomException
|
||||||
from src.core.logger import logger
|
from src.core.logger import logger
|
||||||
from src.schemas.errorResponse import ErrorResponse
|
from src.schemas.errorResponse import ErrorResponse
|
||||||
|
|
||||||
|
|
||||||
uvicorn_logger = logging.getLogger("uvicorn")
|
uvicorn_logger = logging.getLogger("uvicorn")
|
||||||
|
|
||||||
for handler in uvicorn_logger.handlers:
|
for handler in uvicorn_logger.handlers:
|
||||||
uvicorn_logger.removeHandler(handler)
|
uvicorn_logger.removeHandler(handler)
|
||||||
for handler in logger._error_logger.handlers:
|
for handler in logger._error_logger.handlers:
|
||||||
uvicorn_logger.addHandler(handler)
|
uvicorn_logger.addHandler(handler)
|
||||||
|
|
||||||
uvicorn_logger.setLevel(logging.INFO)
|
uvicorn_logger.setLevel(logging.INFO)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import re
|
import re
|
||||||
from typing import Optional, List,Union
|
from typing import Optional, List
|
||||||
from pydantic import BaseModel, Field, validator
|
from pydantic import BaseModel, Field, validator
|
||||||
|
|
||||||
from src.core.exception import CustomException
|
from src.core.exception import CustomException
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
|
|
||||||
|
import ipaddress
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
|
@ -6,6 +7,7 @@ from src.core.config import ConfigManager
|
||||||
from src.core.envHelper import EnvHelper
|
from src.core.envHelper import EnvHelper
|
||||||
from src.core.exception import CustomException
|
from src.core.exception import CustomException
|
||||||
from src.schemas.appInstall import appInstall
|
from src.schemas.appInstall import appInstall
|
||||||
|
from src.schemas.appResponse import AppResponse
|
||||||
from src.services.git_manager import GitManager
|
from src.services.git_manager import GitManager
|
||||||
from src.services.gitea_manager import GiteaManager
|
from src.services.gitea_manager import GiteaManager
|
||||||
from src.services.portainer_manager import PortainerManager
|
from src.services.portainer_manager import PortainerManager
|
||||||
|
@ -16,26 +18,223 @@ from src.utils.password_generator import PasswordGenerator
|
||||||
|
|
||||||
|
|
||||||
class AppManger:
|
class AppManger:
|
||||||
def install_app(self,appInstall: appInstall, endpointId: int = None):
|
def get_catalog_apps(self,locale:str):
|
||||||
library_path = ConfigManager().get_value("docker_library", "path")
|
try:
|
||||||
portainerManager = PortainerManager()
|
# Get the app media path
|
||||||
|
base_path = ConfigManager().get_value("app_media", "path")
|
||||||
# if endpointId is None, get the local endpointId
|
app_media_path = base_path + 'catalog_' + locale + '.json'
|
||||||
if endpointId is None:
|
# check the app media path is exists
|
||||||
try:
|
if not os.path.exists(app_media_path):
|
||||||
endpointId = portainerManager.get_local_endpoint_id()
|
logger.error(f"Get catalog apps error: {app_media_path} is not exists")
|
||||||
except (CustomException,Exception) as e:
|
|
||||||
raise CustomException()
|
raise CustomException()
|
||||||
else :
|
|
||||||
# validate the endpointId is exists
|
# Get the app catalog list
|
||||||
is_endpointId_exists = portainerManager.check_endpoint_exists(endpointId)
|
with open(app_media_path, "r") as f:
|
||||||
|
data = json.load(f)
|
||||||
|
return data
|
||||||
|
except (CustomException,Exception) as e:
|
||||||
|
logger.error(f"Get catalog apps error:{e}")
|
||||||
|
raise CustomException()
|
||||||
|
|
||||||
if not is_endpointId_exists:
|
def get_available_apps(self,locale:str):
|
||||||
raise CustomException(
|
try:
|
||||||
status_code=404,
|
# Get the app media path
|
||||||
message="Invalid Request",
|
base_path = ConfigManager().get_value("app_media", "path")
|
||||||
details="EndpointId Not Found"
|
app_media_path = base_path + 'product_' + locale + '.json'
|
||||||
|
# check the app media path is exists
|
||||||
|
if not os.path.exists(app_media_path):
|
||||||
|
logger.error(f"Get available apps error: {app_media_path} is not exists")
|
||||||
|
raise CustomException()
|
||||||
|
|
||||||
|
# Get the app available list
|
||||||
|
with open(app_media_path, "r") as f:
|
||||||
|
data = json.load(f)
|
||||||
|
# appAvailableResponses = [AppAvailableResponse(**item) for item in data]
|
||||||
|
# return appAvailableResponses
|
||||||
|
return data
|
||||||
|
except (CustomException,Exception) as e:
|
||||||
|
logger.error(f"Get available apps error:{e}")
|
||||||
|
raise CustomException()
|
||||||
|
|
||||||
|
def get_apps(self,endpointId:int = None):
|
||||||
|
portainerManager = PortainerManager()
|
||||||
|
|
||||||
|
# Check the endpointId is exists.
|
||||||
|
endpointId = self._check_endpointId(endpointId, portainerManager)
|
||||||
|
|
||||||
|
try:
|
||||||
|
apps_info = []
|
||||||
|
# Get the stacks
|
||||||
|
stacks = portainerManager.get_stacks(endpointId)
|
||||||
|
for stack in stacks:
|
||||||
|
stack_name = stack.get("Name",None)
|
||||||
|
if stack_name is not None:
|
||||||
|
app_info = self.get_app_by_id(stack_name,endpointId)
|
||||||
|
apps_info.append(app_info)
|
||||||
|
|
||||||
|
# Get the not stacks(not installed apps)
|
||||||
|
all_containers = portainerManager.get_containers(endpointId)
|
||||||
|
# Get the not stacks
|
||||||
|
not_stacks = []
|
||||||
|
for container in all_containers:
|
||||||
|
container_labels = container.get("Labels",None)
|
||||||
|
if container_labels is not None:
|
||||||
|
container_project = container_labels.get("com.docker.compose.project",None)
|
||||||
|
if container_project is not None:
|
||||||
|
if not any(container_project in stack.get("Name",[]) for stack in stacks):
|
||||||
|
not_stacks.append(container_project)
|
||||||
|
# Remove the duplicate elements
|
||||||
|
not_stacks = list(set(not_stacks))
|
||||||
|
# Remove the websoft9
|
||||||
|
if "websoft9" in not_stacks:
|
||||||
|
not_stacks.remove("websoft9")
|
||||||
|
# Get the not stacks info
|
||||||
|
for not_stack in not_stacks:
|
||||||
|
not_stack_response = AppResponse(
|
||||||
|
app_id=not_stack,
|
||||||
|
app_official=False,
|
||||||
)
|
)
|
||||||
|
apps_info.append(not_stack_response)
|
||||||
|
|
||||||
|
return apps_info
|
||||||
|
except (CustomException,Exception) as e:
|
||||||
|
logger.error(f"Get apps error:{e}")
|
||||||
|
raise CustomException()
|
||||||
|
|
||||||
|
def get_app_by_id(self,app_id:str,endpointId:int = None):
|
||||||
|
portainerManager = PortainerManager()
|
||||||
|
|
||||||
|
# Check the endpointId is exists.
|
||||||
|
endpointId = self._check_endpointId(endpointId, portainerManager)
|
||||||
|
|
||||||
|
# validate the app_id is exists in portainer
|
||||||
|
is_stack_exists = portainerManager.check_stack_exists(app_id,endpointId)
|
||||||
|
if not is_stack_exists:
|
||||||
|
raise CustomException(
|
||||||
|
status_code=400,
|
||||||
|
message="Invalid Request",
|
||||||
|
details=f"{app_id} Not Found"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Get stack_info
|
||||||
|
stack_info = portainerManager.get_stack_by_name(app_id,endpointId)
|
||||||
|
# Get the stack_id
|
||||||
|
stack_id = stack_info.get("Id",None)
|
||||||
|
# Get the stack_status
|
||||||
|
stack_status = stack_info.get("Status",0)
|
||||||
|
# Get the gitConfig
|
||||||
|
gitConfig = stack_info.get("GitConfig",{}) or {}
|
||||||
|
# Get the creationDate
|
||||||
|
creationDate = stack_info.get("CreationDate","")
|
||||||
|
if stack_id is None:
|
||||||
|
raise CustomException(
|
||||||
|
status_code=400,
|
||||||
|
message="Invalid Request",
|
||||||
|
details=f"{app_id} Not Found"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Get the domain_names
|
||||||
|
domain_names = ProxyManager().get_proxy_host_by_app(app_id)
|
||||||
|
# Get the proxy_enabled
|
||||||
|
if not domain_names:
|
||||||
|
proxy_enabled = False
|
||||||
|
else :
|
||||||
|
proxy_enabled = True
|
||||||
|
# Get the volumes
|
||||||
|
app_volumes = portainerManager.get_volumes_by_stack_name(app_id,endpointId,False)
|
||||||
|
|
||||||
|
# if stack is empty(status=2-inactive),can not get it
|
||||||
|
if stack_status == 1:
|
||||||
|
# Get the containers
|
||||||
|
app_containers = portainerManager.get_containers_by_stack_name(app_id,endpointId)
|
||||||
|
|
||||||
|
# Get the main container
|
||||||
|
main_container_id = None
|
||||||
|
for container in app_containers:
|
||||||
|
if f"/{app_id}" in container.get("Names", []):
|
||||||
|
main_container_id = container.get("Id", "")
|
||||||
|
break
|
||||||
|
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 http port from env
|
||||||
|
app_http_port = None
|
||||||
|
app_name = None
|
||||||
|
app_dist = None
|
||||||
|
for item in app_env:
|
||||||
|
key, value = item.split("=", 1)
|
||||||
|
if key == "APP_HTTP_PORT":
|
||||||
|
app_http_port = value
|
||||||
|
elif key == "APP_NAME":
|
||||||
|
app_name = value
|
||||||
|
elif key == "APP_DIST":
|
||||||
|
app_dist = value
|
||||||
|
elif key == "APP_VERSION":
|
||||||
|
app_version = value
|
||||||
|
|
||||||
|
# Get the app_port
|
||||||
|
app_port = None
|
||||||
|
if app_http_port:
|
||||||
|
internal_port_str = str(app_http_port) + "/tcp"
|
||||||
|
port_mappings = main_container_info["NetworkSettings"]["Ports"].get(internal_port_str, [])
|
||||||
|
for mapping in port_mappings:
|
||||||
|
try:
|
||||||
|
ipaddress.IPv4Address(mapping["HostIp"])
|
||||||
|
app_port = mapping["HostPort"]
|
||||||
|
except ipaddress.AddressValueError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
appResponse = AppResponse(
|
||||||
|
app_id = app_id,
|
||||||
|
endpointId = endpointId,
|
||||||
|
app_name = app_name,
|
||||||
|
app_port = app_port,
|
||||||
|
app_dist = app_dist,
|
||||||
|
app_version = app_version,
|
||||||
|
app_official = True,
|
||||||
|
proxy_enabled = proxy_enabled,
|
||||||
|
domain_names = domain_names,
|
||||||
|
status = stack_status,
|
||||||
|
creationDate = creationDate,
|
||||||
|
gitConfig = gitConfig,
|
||||||
|
containers = app_containers,
|
||||||
|
volumes = app_volumes,
|
||||||
|
env = app_env
|
||||||
|
)
|
||||||
|
return appResponse
|
||||||
|
else:
|
||||||
|
appResponse = AppResponse(
|
||||||
|
app_id = app_id,
|
||||||
|
endpointId = endpointId,
|
||||||
|
app_name = "",
|
||||||
|
app_port = 0,
|
||||||
|
app_dist = "",
|
||||||
|
app_version = "",
|
||||||
|
app_official = True,
|
||||||
|
proxy_enabled = proxy_enabled,
|
||||||
|
domain_names = domain_names,
|
||||||
|
status = stack_status,
|
||||||
|
creationDate = creationDate,
|
||||||
|
gitConfig = gitConfig,
|
||||||
|
containers = [],
|
||||||
|
volumes = app_volumes,
|
||||||
|
env = []
|
||||||
|
)
|
||||||
|
return appResponse
|
||||||
|
|
||||||
|
def install_app(self,appInstall: appInstall, endpointId: int = None):
|
||||||
|
# Get the library path
|
||||||
|
library_path = ConfigManager().get_value("docker_library", "path")
|
||||||
|
|
||||||
|
# Get the portainer and gitea manager
|
||||||
|
portainerManager = PortainerManager()
|
||||||
|
giteaManager = GiteaManager()
|
||||||
|
|
||||||
|
# Check the endpointId is exists.
|
||||||
|
endpointId = self._check_endpointId(endpointId, portainerManager)
|
||||||
|
|
||||||
# validate the app_name and app_version
|
# validate the app_name and app_version
|
||||||
app_name = appInstall.app_name
|
app_name = appInstall.app_name
|
||||||
|
@ -44,29 +243,27 @@ class AppManger:
|
||||||
|
|
||||||
# validate the app_id
|
# validate the app_id
|
||||||
app_id = appInstall.app_id
|
app_id = appInstall.app_id
|
||||||
self._check_appId(app_id,endpointId)
|
self._check_appId(app_id,endpointId,giteaManager,portainerManager)
|
||||||
|
|
||||||
proxy_enabled = appInstall.proxy_enabled
|
|
||||||
domain_names = appInstall.domain_names
|
|
||||||
|
|
||||||
# validate the domain_names
|
# validate the domain_names
|
||||||
|
proxy_enabled = appInstall.proxy_enabled
|
||||||
|
domain_names = appInstall.domain_names
|
||||||
if proxy_enabled:
|
if proxy_enabled:
|
||||||
self._check_domain_names(domain_names)
|
self._check_domain_names(domain_names)
|
||||||
|
|
||||||
# Begin install app
|
# Install app - Step 1 : create repo in gitea
|
||||||
# Step 1 : create repo in gitea
|
|
||||||
giteaManager = GiteaManager()
|
|
||||||
repo_url = giteaManager.create_repo(app_id)
|
repo_url = giteaManager.create_repo(app_id)
|
||||||
|
|
||||||
# Step 2 : initialize local git repo and push to gitea
|
# Install app - Step 2 : initialize local git repo and push to gitea
|
||||||
try:
|
try:
|
||||||
|
# The source directory.
|
||||||
local_path = f"{library_path}/{app_name}"
|
local_path = f"{library_path}/{app_name}"
|
||||||
|
|
||||||
# The destination directory.
|
# Create a temporary directory.
|
||||||
app_tmp_dir = "/tmp"
|
app_tmp_dir = "/tmp"
|
||||||
app_tmp_dir_path = f"{app_tmp_dir}/{app_name}"
|
app_tmp_dir_path = f"{app_tmp_dir}/{app_name}"
|
||||||
|
|
||||||
# Check if the destination directory exists, create it if necessary.
|
# If the temporary directory does not exist, create it.
|
||||||
if not os.path.exists(app_tmp_dir):
|
if not os.path.exists(app_tmp_dir):
|
||||||
os.makedirs(app_tmp_dir)
|
os.makedirs(app_tmp_dir)
|
||||||
|
|
||||||
|
@ -80,49 +277,288 @@ class AppManger:
|
||||||
# Modify the env file
|
# Modify the env file
|
||||||
env_file_path = f"{app_tmp_dir_path}/.env"
|
env_file_path = f"{app_tmp_dir_path}/.env"
|
||||||
new_env_values = {
|
new_env_values = {
|
||||||
"APP_NAME": app_id,
|
"APP_ID": app_id,
|
||||||
|
"APP_NAME": app_name,
|
||||||
|
"APP_DIST": "community",
|
||||||
"APP_VERSION": app_version,
|
"APP_VERSION": app_version,
|
||||||
"POWER_PASSWORD": PasswordGenerator.generate_strong_password()
|
"POWER_PASSWORD": PasswordGenerator.generate_strong_password(),
|
||||||
}
|
"APP_URL": domain_names[0]
|
||||||
new_env_values["APP_URL"] = domain_names[0]
|
}
|
||||||
EnvHelper(env_file_path).modify_env_values(new_env_values)
|
EnvHelper(env_file_path).modify_env_values(new_env_values)
|
||||||
|
|
||||||
# Get the forward port form env file
|
|
||||||
forward_port = EnvHelper(env_file_path).get_env_value_by_key("APP_HTTP_PORT")
|
|
||||||
|
|
||||||
# Commit and push to remote repo
|
# Commit and push to remote repo
|
||||||
self._init_local_repo_and_push_to_remote(app_tmp_dir_path,repo_url)
|
self._init_local_repo_and_push_to_remote(app_tmp_dir_path,repo_url)
|
||||||
|
|
||||||
# Remove the tmp dir
|
|
||||||
shutil.rmtree(app_tmp_dir_path)
|
|
||||||
except (CustomException,Exception) as e:
|
except (CustomException,Exception) as e:
|
||||||
# Rollback: remove repo in gitea
|
# Rollback: remove repo in gitea
|
||||||
giteaManager.remove_repo(app_id)
|
giteaManager.remove_repo(app_id)
|
||||||
raise CustomException()
|
raise CustomException()
|
||||||
|
|
||||||
# Step 3 : create stack in portainer
|
# Install app - Step 3 : create stack in portainer
|
||||||
try:
|
try:
|
||||||
|
# Get gitea user_name and user_pwd
|
||||||
user_name = ConfigManager().get_value("gitea","user_name")
|
user_name = ConfigManager().get_value("gitea","user_name")
|
||||||
user_pwd = ConfigManager().get_value("gitea","user_pwd")
|
user_pwd = ConfigManager().get_value("gitea","user_pwd")
|
||||||
portainerManager.create_stack_from_repository(app_id,endpointId,repo_url,user_name,user_pwd)
|
# Create stack in portainer
|
||||||
stack_id = portainerManager.get_stack_by_name(app_id,endpointId)["Id"]
|
stack_info = portainerManager.create_stack_from_repository(app_id,endpointId,repo_url,user_name,user_pwd)
|
||||||
|
# Get the stack_id
|
||||||
|
stack_id = stack_info.get("Id")
|
||||||
except (CustomException,Exception) as e:
|
except (CustomException,Exception) as e:
|
||||||
# Rollback: remove repo in gitea
|
# Rollback: remove repo in gitea
|
||||||
giteaManager.remove_repo(app_id)
|
giteaManager.remove_repo(app_id)
|
||||||
raise CustomException()
|
raise CustomException()
|
||||||
|
|
||||||
# Step 4 : create proxy in proxy
|
# Install app - Step 4 : create proxy in nginx proxy manager
|
||||||
try:
|
try:
|
||||||
if domain_names:
|
if proxy_enabled and domain_names:
|
||||||
ProxyManager().create_proxy_for_app(domain_names,app_id,forward_port)
|
# Get the forward port form env file
|
||||||
except (CustomException,Exception) as e:
|
forward_port = EnvHelper(env_file_path).get_env_value_by_key("APP_HTTP_PORT")
|
||||||
|
# Get the nginx proxy config path
|
||||||
|
nginx_proxy_path = f"{app_tmp_dir_path}/src/nginx-proxy.conf"
|
||||||
|
if os.path.exists(nginx_proxy_path):
|
||||||
|
# Get the advanced config
|
||||||
|
advanced_config = FileHelper.read_file(nginx_proxy_path)
|
||||||
|
ProxyManager().create_proxy_for_app(domain_names,app_id,forward_port,advanced_config)
|
||||||
|
else:
|
||||||
|
ProxyManager().create_proxy_for_app(domain_names,app_id,forward_port)
|
||||||
|
except (CustomException,Exception) as e:
|
||||||
# Rollback-1: remove repo in gitea
|
# Rollback-1: remove repo in gitea
|
||||||
giteaManager.remove_repo(app_id)
|
giteaManager.remove_repo(app_id)
|
||||||
# Rollback-2: remove stack in portainer
|
# Rollback-2: remove stack in portainer
|
||||||
portainerManager.remove_stack_and_volumes(stack_id,endpointId)
|
portainerManager.remove_stack_and_volumes(stack_id,endpointId)
|
||||||
raise CustomException()
|
raise CustomException()
|
||||||
|
|
||||||
|
# Remove the tmp dir
|
||||||
|
shutil.rmtree(app_tmp_dir_path)
|
||||||
|
|
||||||
|
return self.get_app_by_id(app_id,endpointId)
|
||||||
|
|
||||||
|
def redeploy_app(self,app_id:str,pull_image:bool,endpointId:int = None):
|
||||||
|
portainerManager = PortainerManager()
|
||||||
|
|
||||||
|
# Check the endpointId is exists.
|
||||||
|
endpointId = self._check_endpointId(endpointId, portainerManager)
|
||||||
|
|
||||||
|
# validate the app_id is exists in portainer
|
||||||
|
is_stack_exists = portainerManager.check_stack_exists(app_id,endpointId)
|
||||||
|
if not is_stack_exists:
|
||||||
|
raise CustomException(
|
||||||
|
status_code=400,
|
||||||
|
message="Invalid Request",
|
||||||
|
details=f"{app_id} Not Found"
|
||||||
|
)
|
||||||
|
# Get stack_id
|
||||||
|
stack_id = portainerManager.get_stack_by_name(app_id,endpointId).get("Id",None)
|
||||||
|
if stack_id is None:
|
||||||
|
raise CustomException(
|
||||||
|
status_code=400,
|
||||||
|
message="Invalid Request",
|
||||||
|
details=f"{app_id} Not Found"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
user_name = ConfigManager().get_value("gitea","user_name")
|
||||||
|
user_pwd = ConfigManager().get_value("gitea","user_pwd")
|
||||||
|
# redeploy stack
|
||||||
|
portainerManager.redeploy_stack(stack_id,endpointId,pull_image,user_name,user_pwd)
|
||||||
|
|
||||||
|
def uninstall_app(self,app_id:str,purge_data:bool,endpointId:int = None):
|
||||||
|
portainerManager = PortainerManager()
|
||||||
|
|
||||||
|
# Check the endpointId is exists.
|
||||||
|
endpointId = self._check_endpointId(endpointId, portainerManager)
|
||||||
|
|
||||||
|
# validate the app_id is exists in portainer
|
||||||
|
is_stack_exists = portainerManager.check_stack_exists(app_id,endpointId)
|
||||||
|
if not is_stack_exists:
|
||||||
|
raise CustomException(
|
||||||
|
status_code=400,
|
||||||
|
message="Invalid Request",
|
||||||
|
details=f"{app_id} Not Found"
|
||||||
|
)
|
||||||
|
# Get stack_id
|
||||||
|
stack_id = portainerManager.get_stack_by_name(app_id,endpointId).get("Id",None)
|
||||||
|
if stack_id is None:
|
||||||
|
raise CustomException(
|
||||||
|
status_code=400,
|
||||||
|
message="Invalid Request",
|
||||||
|
details=f"{app_id} Not Found"
|
||||||
|
)
|
||||||
|
|
||||||
|
# get stack status,if stack is empty(status=2-inactive),can not uninstall it
|
||||||
|
stack_status = portainerManager.get_stack_by_name(app_id,endpointId).get("Status")
|
||||||
|
if stack_status == 2:
|
||||||
|
raise CustomException(
|
||||||
|
status_code=400,
|
||||||
|
message="Invalid Request",
|
||||||
|
details=f"{app_id} is empty, can not uninstall it,you can remove it"
|
||||||
|
)
|
||||||
|
|
||||||
|
if purge_data:
|
||||||
|
# Uninstall app - Step 1 : remove proxy in nginx proxy manager
|
||||||
|
# Check the proxy is exists
|
||||||
|
proxyManager = ProxyManager()
|
||||||
|
proxys_host = proxyManager.get_proxy_host_by_app(app_id)
|
||||||
|
# If the proxy is exists, remove it
|
||||||
|
if proxys_host:
|
||||||
|
proxyManager.remove_proxy_host_for_app(app_id)
|
||||||
|
|
||||||
|
# Uninstall app - Step 2 : remove repo in gitea
|
||||||
|
# Check the repo is exists
|
||||||
|
giteaManager = GiteaManager()
|
||||||
|
is_repo_exists = giteaManager.check_repo_exists(app_id)
|
||||||
|
if is_repo_exists:
|
||||||
|
giteaManager.remove_repo(app_id)
|
||||||
|
|
||||||
|
# Uninstall app - Step 3 : remove stack in portainer
|
||||||
|
# Get stack_id
|
||||||
|
stack_id = portainerManager.get_stack_by_name(app_id,endpointId).get("Id",None)
|
||||||
|
if stack_id is None:
|
||||||
|
raise CustomException(
|
||||||
|
status_code=400,
|
||||||
|
message="Invalid Request",
|
||||||
|
details=f"{app_id} Not Found"
|
||||||
|
)
|
||||||
|
# remove stack and volumes
|
||||||
|
portainerManager.remove_stack_and_volumes(stack_id,endpointId)
|
||||||
|
else:
|
||||||
|
# down stack
|
||||||
|
portainerManager.down_stack(stack_id,endpointId)
|
||||||
|
|
||||||
|
def remove_app(self,app_id:str,endpointId:int = None):
|
||||||
|
portainerManager = PortainerManager()
|
||||||
|
|
||||||
|
# Check the endpointId is exists.
|
||||||
|
endpointId = self._check_endpointId(endpointId, portainerManager)
|
||||||
|
|
||||||
|
# validate the app_id is exists in portainer
|
||||||
|
is_stack_exists = portainerManager.check_stack_exists(app_id,endpointId)
|
||||||
|
if not is_stack_exists:
|
||||||
|
raise CustomException(
|
||||||
|
status_code=400,
|
||||||
|
message="Invalid Request",
|
||||||
|
details=f"{app_id} Not Found"
|
||||||
|
)
|
||||||
|
# Get stack_id
|
||||||
|
stack_id = portainerManager.get_stack_by_name(app_id,endpointId).get("Id",None)
|
||||||
|
if stack_id is None:
|
||||||
|
raise CustomException(
|
||||||
|
status_code=400,
|
||||||
|
message="Invalid Request",
|
||||||
|
details=f"{app_id} Not Found"
|
||||||
|
)
|
||||||
|
# get stack status,if stack is not empty(status=1-active),can not remove it
|
||||||
|
stack_status = portainerManager.get_stack_by_name(app_id,endpointId).get("Status")
|
||||||
|
if stack_status == 1:
|
||||||
|
raise CustomException(
|
||||||
|
status_code=400,
|
||||||
|
message="Invalid Request",
|
||||||
|
details=f"{app_id} is not empty, please uninstall it first"
|
||||||
|
)
|
||||||
|
# Check the proxy is exists
|
||||||
|
proxyManager = ProxyManager()
|
||||||
|
proxys_host = proxyManager.get_proxy_host_by_app(app_id)
|
||||||
|
# If the proxy is exists, remove it
|
||||||
|
if proxys_host:
|
||||||
|
proxyManager.remove_proxy_host_for_app(app_id)
|
||||||
|
|
||||||
|
# Check the repo is exists
|
||||||
|
giteaManager = GiteaManager()
|
||||||
|
is_repo_exists = giteaManager.check_repo_exists(app_id)
|
||||||
|
if is_repo_exists:
|
||||||
|
giteaManager.remove_repo(app_id)
|
||||||
|
# remove stack and volumes
|
||||||
|
portainerManager.remove_stack_and_volumes(stack_id,endpointId)
|
||||||
|
|
||||||
|
def start_app(self,app_id:str,endpointId:int = None):
|
||||||
|
portainerManager = PortainerManager()
|
||||||
|
|
||||||
|
# Check the endpointId is exists.
|
||||||
|
endpointId = self._check_endpointId(endpointId, portainerManager)
|
||||||
|
|
||||||
|
# validate the app_id is exists in portainer
|
||||||
|
# is_stack_exists = portainerManager.check_stack_exists(app_id,endpointId)
|
||||||
|
# if not is_stack_exists:
|
||||||
|
# raise CustomException(
|
||||||
|
# status_code=400,
|
||||||
|
# message="Invalid Request",
|
||||||
|
# details=f"{app_id} Not Found"
|
||||||
|
# )
|
||||||
|
stack_info = portainerManager.get_stack_by_name(app_id,endpointId)
|
||||||
|
if stack_info is None:
|
||||||
|
raise CustomException(
|
||||||
|
status_code=400,
|
||||||
|
message="Invalid Request",
|
||||||
|
details=f"{app_id} Not Found"
|
||||||
|
)
|
||||||
|
stack_status = stack_info.get("Status",None)
|
||||||
|
if stack_status == 2:
|
||||||
|
raise CustomException(
|
||||||
|
status_code=400,
|
||||||
|
message="Invalid Request",
|
||||||
|
details=f"{app_id} is empty, can not start it,you can redeploy it"
|
||||||
|
)
|
||||||
|
|
||||||
|
portainerManager.start_stack(app_id,endpointId)
|
||||||
|
|
||||||
|
def stop_app(self,app_id:str,endpointId:int = None):
|
||||||
|
portainerManager = PortainerManager()
|
||||||
|
|
||||||
|
# Check the endpointId is exists.
|
||||||
|
endpointId = self._check_endpointId(endpointId, portainerManager)
|
||||||
|
|
||||||
|
# validate the app_id is exists in portainer
|
||||||
|
# is_stack_exists = portainerManager.check_stack_exists(app_id,endpointId)
|
||||||
|
# if not is_stack_exists:
|
||||||
|
# raise CustomException(
|
||||||
|
# status_code=400,
|
||||||
|
# message="Invalid Request",
|
||||||
|
# details=f"{app_id} Not Found"
|
||||||
|
# )
|
||||||
|
stack_info = portainerManager.get_stack_by_name(app_id,endpointId)
|
||||||
|
if stack_info is None:
|
||||||
|
raise CustomException(
|
||||||
|
status_code=400,
|
||||||
|
message="Invalid Request",
|
||||||
|
details=f"{app_id} Not Found"
|
||||||
|
)
|
||||||
|
stack_status = stack_info.get("Status",None)
|
||||||
|
if stack_status == 2:
|
||||||
|
raise CustomException(
|
||||||
|
status_code=400,
|
||||||
|
message="Invalid Request",
|
||||||
|
details=f"{app_id} is empty, can not stop it,you can redeploy it"
|
||||||
|
)
|
||||||
|
portainerManager.stop_stack(app_id,endpointId)
|
||||||
|
|
||||||
|
def restart_app(self,app_id:str,endpointId:int = None):
|
||||||
|
portainerManager = PortainerManager()
|
||||||
|
|
||||||
|
# Check the endpointId is exists.
|
||||||
|
endpointId = self._check_endpointId(endpointId, portainerManager)
|
||||||
|
|
||||||
|
# validate the app_id is exists in portainer
|
||||||
|
# is_stack_exists = portainerManager.check_stack_exists(app_id,endpointId)
|
||||||
|
# if not is_stack_exists:
|
||||||
|
# raise CustomException(
|
||||||
|
# status_code=400,
|
||||||
|
# message="Invalid Request",
|
||||||
|
# details=f"{app_id} Not Found"
|
||||||
|
# )
|
||||||
|
stack_info = portainerManager.get_stack_by_name(app_id,endpointId)
|
||||||
|
if stack_info is None:
|
||||||
|
raise CustomException(
|
||||||
|
status_code=400,
|
||||||
|
message="Invalid Request",
|
||||||
|
details=f"{app_id} Not Found"
|
||||||
|
)
|
||||||
|
stack_status = stack_info.get("Status",None)
|
||||||
|
if stack_status == 2:
|
||||||
|
raise CustomException(
|
||||||
|
status_code=400,
|
||||||
|
message="Invalid Request",
|
||||||
|
details=f"{app_id} is empty, can not restart it,you can redeploy it"
|
||||||
|
)
|
||||||
|
portainerManager.restart_stack(app_id,endpointId)
|
||||||
|
|
||||||
def _check_appName_and_appVersion(self,app_name:str, app_version:str,library_path:str):
|
def _check_appName_and_appVersion(self,app_name:str, app_version:str,library_path:str):
|
||||||
"""
|
"""
|
||||||
Check the app_name and app_version is exists in docker library
|
Check the app_name and app_version is exists in docker library
|
||||||
|
@ -142,20 +578,21 @@ class AppManger:
|
||||||
details=f"app_name:{app_name} not supported",
|
details=f"app_name:{app_name} not supported",
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
with open(f"{library_path}/{app_name}/variables.json", "r") as f:
|
|
||||||
variables = json.load(f)
|
with open(f"{library_path}/{app_name}/variables.json", "r") as f:
|
||||||
community_editions = [d for d in variables["edition"] if d["dist"] == "community"]
|
variables = json.load(f)
|
||||||
if not any(
|
community_editions = [d for d in variables["edition"] if d["dist"] == "community"]
|
||||||
app_version in d["version"] for d in community_editions
|
if not any(
|
||||||
):
|
app_version in d["version"] for d in community_editions
|
||||||
logger.error(f"When install app:{app_name}, the app version:{app_version} is not exists in docker library")
|
):
|
||||||
raise CustomException(
|
logger.error(f"When install app:{app_name}, the app version:{app_version} is not exists in docker library")
|
||||||
status_code=400,
|
raise CustomException(
|
||||||
message="Invalid Request",
|
status_code=400,
|
||||||
details=f"app_version:{app_version} not supported",
|
message="Invalid Request",
|
||||||
)
|
details=f"app_version:{app_version} not supported",
|
||||||
|
)
|
||||||
|
|
||||||
def _check_appId(self,app_id:str,endpointId:int):
|
def _check_appId(self,app_id:str,endpointId:int,giteaManager:GiteaManager,portainerManager:PortainerManager):
|
||||||
"""
|
"""
|
||||||
Check the app_id is exists in gitea and portainer
|
Check the app_id is exists in gitea and portainer
|
||||||
|
|
||||||
|
@ -167,7 +604,6 @@ class AppManger:
|
||||||
CustomException: If the app_id is exists in gitea or portainer
|
CustomException: If the app_id is exists in gitea or portainer
|
||||||
"""
|
"""
|
||||||
# validate the app_id is exists in gitea
|
# validate the app_id is exists in gitea
|
||||||
giteaManager = GiteaManager()
|
|
||||||
is_repo_exists = giteaManager.check_repo_exists(app_id)
|
is_repo_exists = giteaManager.check_repo_exists(app_id)
|
||||||
if is_repo_exists:
|
if is_repo_exists:
|
||||||
logger.error(f"When install app,the app_id:{{app_id}} is exists in gitea")
|
logger.error(f"When install app,the app_id:{{app_id}} is exists in gitea")
|
||||||
|
@ -178,7 +614,6 @@ class AppManger:
|
||||||
)
|
)
|
||||||
|
|
||||||
# validate the app_id is exists in portainer
|
# validate the app_id is exists in portainer
|
||||||
portainerManager = PortainerManager()
|
|
||||||
is_stack_exists = portainerManager.check_stack_exists(app_id,endpointId)
|
is_stack_exists = portainerManager.check_stack_exists(app_id,endpointId)
|
||||||
if is_stack_exists:
|
if is_stack_exists:
|
||||||
logger.error(f"When install app, the app_id:{app_id} is exists in portainer")
|
logger.error(f"When install app, the app_id:{app_id} is exists in portainer")
|
||||||
|
@ -217,3 +652,28 @@ class AppManger:
|
||||||
except (CustomException,Exception) as e:
|
except (CustomException,Exception) as e:
|
||||||
logger.error(f"Init local repo and push to remote repo error:{e}")
|
logger.error(f"Init local repo and push to remote repo error:{e}")
|
||||||
raise CustomException()
|
raise CustomException()
|
||||||
|
|
||||||
|
def _check_endpointId(self, endpointId, portainerManager):
|
||||||
|
"""
|
||||||
|
Check the endpointId is exists
|
||||||
|
|
||||||
|
Args:
|
||||||
|
endpointId ([type]): [description]
|
||||||
|
portainerManager ([type]): [description]
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
CustomException: If the endpointId is not exists
|
||||||
|
"""
|
||||||
|
if endpointId is None:
|
||||||
|
# Get the local endpointId
|
||||||
|
endpointId = portainerManager.get_local_endpoint_id()
|
||||||
|
else :
|
||||||
|
# validate the endpointId is exists
|
||||||
|
is_endpointId_exists = portainerManager.check_endpoint_exists(endpointId)
|
||||||
|
if not is_endpointId_exists:
|
||||||
|
raise CustomException(
|
||||||
|
status_code=400,
|
||||||
|
message="Invalid Request",
|
||||||
|
details="EndpointId Not Found"
|
||||||
|
)
|
||||||
|
return endpointId
|
|
@ -1,5 +1,4 @@
|
||||||
import os
|
import os
|
||||||
import shutil
|
|
||||||
from git import Repo, GitCommandError
|
from git import Repo, GitCommandError
|
||||||
from src.core.exception import CustomException
|
from src.core.exception import CustomException
|
||||||
from src.core.logger import logger
|
from src.core.logger import logger
|
||||||
|
@ -15,7 +14,6 @@ class GitManager:
|
||||||
Methods:
|
Methods:
|
||||||
init_local_repo_from_dir() -> None: Initialize a local git repository from a directory.
|
init_local_repo_from_dir() -> None: Initialize a local git repository from a directory.
|
||||||
push_local_repo_to_remote_repo(remote_url:str,user_name:str,user_pwd:str) -> None: Push a local git repository to a remote origin.
|
push_local_repo_to_remote_repo(remote_url:str,user_name:str,user_pwd:str) -> None: Push a local git repository to a remote origin.
|
||||||
remove_git_directory() -> None: Remove the .git directory.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self,local_path:str):
|
def __init__(self,local_path:str):
|
||||||
|
@ -90,5 +88,4 @@ class GitManager:
|
||||||
except GitCommandError as e:
|
except GitCommandError as e:
|
||||||
logger.error(f"Failed to push from 'main' branch in git repository at {self.local_path} to remote '{remote_url}': {str(e)}")
|
logger.error(f"Failed to push from 'main' branch in git repository at {self.local_path} to remote '{remote_url}': {str(e)}")
|
||||||
raise CustomException()
|
raise CustomException()
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,13 @@ from src.external.gitea_api import GiteaAPI
|
||||||
|
|
||||||
|
|
||||||
class GiteaManager:
|
class GiteaManager:
|
||||||
|
"""
|
||||||
|
Gitea Manager
|
||||||
|
"""
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
"""
|
||||||
|
Init GiteaManager
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
self.gitea = GiteaAPI()
|
self.gitea = GiteaAPI()
|
||||||
self._set_basic_auth_credential()
|
self._set_basic_auth_credential()
|
||||||
|
@ -43,7 +49,7 @@ class GiteaManager:
|
||||||
elif response.status_code == 404:
|
elif response.status_code == 404:
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
logger.error(f"Error validate repo is exist from gitea: {response.text}")
|
logger.error(f"Check repo:{repo_name} exists error:{response.status_code}:{response.text}")
|
||||||
raise CustomException()
|
raise CustomException()
|
||||||
|
|
||||||
def create_repo(self, repo_name: str):
|
def create_repo(self, repo_name: str):
|
||||||
|
@ -61,7 +67,7 @@ class GiteaManager:
|
||||||
repo_json = response.json()
|
repo_json = response.json()
|
||||||
return repo_json["clone_url"]
|
return repo_json["clone_url"]
|
||||||
else:
|
else:
|
||||||
logger.error(f"Error create repo from gitea: {response.text}")
|
logger.error(f"Create repo:{repo_name} error:{response.status_code}:{response.text}")
|
||||||
raise CustomException()
|
raise CustomException()
|
||||||
|
|
||||||
def get_file_content_from_repo(self, repo_name: str, file_path: str):
|
def get_file_content_from_repo(self, repo_name: str, file_path: str):
|
||||||
|
@ -78,17 +84,17 @@ class GiteaManager:
|
||||||
"content": response_json["content"],
|
"content": response_json["content"],
|
||||||
}
|
}
|
||||||
else:
|
else:
|
||||||
logger.error(f"Error get file content from repo from gitea: {response.text}")
|
logger.error(f"Get file:{file_path} content from repo:{repo_name} error:{response.status_code}:{response.text}")
|
||||||
raise CustomException()
|
raise CustomException()
|
||||||
|
|
||||||
def update_file_in_repo(self, repo_name: str, file_path: str, content: str,sha: str):
|
def update_file_in_repo(self, repo_name: str, file_path: str, content: str,sha: str):
|
||||||
response = self.gitea.update_file_content_in_repo(repo_name, file_path, content, sha)
|
response = self.gitea.update_file_content_in_repo(repo_name, file_path, content, sha)
|
||||||
if response.status_code != 201:
|
if response.status_code != 201:
|
||||||
logger.error(f"Error update file in repo from gitea: {response.text}")
|
logger.error(f"Update file:{file_path} content in repo:{repo_name} error:{response.status_code}:{response.text}")
|
||||||
raise CustomException()
|
raise CustomException()
|
||||||
|
|
||||||
def remove_repo(self, repo_name: str):
|
def remove_repo(self, repo_name: str):
|
||||||
response = self.gitea.remove_repo(repo_name)
|
response = self.gitea.remove_repo(repo_name)
|
||||||
if response.status_code != 204:
|
if response.status_code != 204:
|
||||||
logger.error(f"Error remove repo from gitea: {response.text}")
|
logger.error(f"Remove repo:{repo_name} error:{response.status_code}:{response.text}")
|
||||||
raise CustomException()
|
raise CustomException()
|
|
@ -9,6 +9,26 @@ from src.core.logger import logger
|
||||||
|
|
||||||
|
|
||||||
class PortainerManager:
|
class PortainerManager:
|
||||||
|
"""
|
||||||
|
Portainer Manager
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
portainer (PortainerAPI): Portainer API
|
||||||
|
|
||||||
|
Methods:
|
||||||
|
_set_portainer_token(): Set Portainer token
|
||||||
|
get_local_endpoint_id(): Get local endpoint id
|
||||||
|
check_endpoint_exists(endpoint_id): Check endpoint exists
|
||||||
|
check_stack_exists(stack_name, endpoint_id): Check stack exists
|
||||||
|
create_stack_from_repository(stack_name, endpoint_id,repositoryURL,user_name,user_password): Create stack from repository
|
||||||
|
get_stacks(endpoint_id): Get stacks
|
||||||
|
get_stack_by_id(stack_id): Get stack by id
|
||||||
|
get_stack_by_name(stack_name, endpoint_id): Get stack by name
|
||||||
|
remove_stack(stack_id, endpoint_id): Remove stack by id
|
||||||
|
remove_stack_and_volumes(stack_id, endpoint_id): Remove stack and volumes by id
|
||||||
|
get_volumes_by_stack_name(stack_name, endpoint_id): Get volumes by stack name
|
||||||
|
remove_volume(volume_names, endpoint_id): Remove volume by name
|
||||||
|
"""
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
try:
|
try:
|
||||||
self.portainer = PortainerAPI()
|
self.portainer = PortainerAPI()
|
||||||
|
@ -18,6 +38,9 @@ class PortainerManager:
|
||||||
raise CustomException()
|
raise CustomException()
|
||||||
|
|
||||||
def _set_portainer_token(self):
|
def _set_portainer_token(self):
|
||||||
|
"""
|
||||||
|
Set Portainer token
|
||||||
|
"""
|
||||||
service_name = "portainer"
|
service_name = "portainer"
|
||||||
token_name = "user_token"
|
token_name = "user_token"
|
||||||
|
|
||||||
|
@ -83,10 +106,10 @@ class PortainerManager:
|
||||||
if local_endpoint is not None:
|
if local_endpoint is not None:
|
||||||
return local_endpoint["Id"]
|
return local_endpoint["Id"]
|
||||||
else:
|
else:
|
||||||
logger.error(f"Error get local endpoint id from portainer: {response.text}")
|
logger.error(f"Can't find local endpoint")
|
||||||
raise CustomException()
|
raise CustomException()
|
||||||
else:
|
else:
|
||||||
logger.error(f"Error get local endpoint id from portainer: {response.text}")
|
logger.error(f"Get local endpoint id error: {response.status_code}:{response.text}")
|
||||||
raise CustomException()
|
raise CustomException()
|
||||||
|
|
||||||
def check_endpoint_exists(self, endpoint_id: int):
|
def check_endpoint_exists(self, endpoint_id: int):
|
||||||
|
@ -96,7 +119,7 @@ class PortainerManager:
|
||||||
elif response.status_code == 404:
|
elif response.status_code == 404:
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
logger.error(f"Error validate endpoint is exist from portainer: {response.text}")
|
logger.error(f"Check endpoint:{endpoint_id} exists error: {response.status_code}:{response.text}")
|
||||||
raise CustomException()
|
raise CustomException()
|
||||||
|
|
||||||
def check_stack_exists(self, stack_name: str, endpoint_id: int):
|
def check_stack_exists(self, stack_name: str, endpoint_id: int):
|
||||||
|
@ -108,29 +131,79 @@ class PortainerManager:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
logger.error(f"Error validate stack is exist from portainer: {response.text}")
|
logger.error(f"Check stack:{stack_name} exists error: {response.status_code}:{response.text}")
|
||||||
raise CustomException()
|
raise CustomException()
|
||||||
|
|
||||||
def create_stack_from_repository(self, stack_name: str, endpoint_id: int,repositoryURL : str,user_name:str,user_password:str):
|
def create_stack_from_repository(self, stack_name: str, endpoint_id: int,repositoryURL : str,user_name:str,user_password:str):
|
||||||
response = self.portainer.create_stack_standlone_repository(stack_name, endpoint_id,repositoryURL,user_name,user_password)
|
response = self.portainer.create_stack_standlone_repository(stack_name, endpoint_id,repositoryURL,user_name,user_password)
|
||||||
if response.status_code != 200:
|
if response.status_code == 200:
|
||||||
logger.error(f"Error create stack from portainer: {response.text}")
|
return response.json()
|
||||||
raise CustomException()
|
else:
|
||||||
|
message = response.text
|
||||||
|
if message:
|
||||||
|
try:
|
||||||
|
response_details = json.loads(message)
|
||||||
|
message = response_details.get('details', 'unknown error')
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
pass
|
||||||
|
logger.error(f"Create stack:{stack_name} from repository:{repositoryURL} error: {response.status_code}:{response.text}")
|
||||||
|
raise CustomException(
|
||||||
|
status_code=400,
|
||||||
|
message="Invalid Request",
|
||||||
|
details=message
|
||||||
|
)
|
||||||
|
|
||||||
|
def redeploy_stack(self, stack_id: int, endpoint_id: int,pull_image:bool,user_name:str,user_password:str):
|
||||||
|
response = self.portainer.redeploy_stack(stack_id, endpoint_id,pull_image,user_name,user_password)
|
||||||
|
if response.status_code == 200:
|
||||||
|
return response.json()
|
||||||
|
else:
|
||||||
|
message = response.text
|
||||||
|
if message:
|
||||||
|
try:
|
||||||
|
response_details = json.loads(message)
|
||||||
|
message = response_details.get('details', 'unknown error')
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
pass
|
||||||
|
logger.error(f"Redeploy stack:{stack_id} error: {response.status_code}:{response.text}")
|
||||||
|
raise CustomException(
|
||||||
|
status_code=400,
|
||||||
|
message="Invalid Request",
|
||||||
|
details=message
|
||||||
|
)
|
||||||
|
|
||||||
def get_stacks(self, endpoint_id: int):
|
def get_stacks(self, endpoint_id: int):
|
||||||
|
"""
|
||||||
|
Get stacks
|
||||||
|
|
||||||
|
Args:
|
||||||
|
endpoint_id (int): endpoint id
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list: stack list
|
||||||
|
"""
|
||||||
response = self.portainer.get_stacks(endpoint_id)
|
response = self.portainer.get_stacks(endpoint_id)
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
return response.json()
|
return response.json()
|
||||||
else:
|
else:
|
||||||
logger.error(f"Error get stacks from portainer: {response.text}")
|
logger.error(f"Get stacks from endpoint:{endpoint_id} error: {response.status_code}:{response.text}")
|
||||||
raise CustomException()
|
raise CustomException()
|
||||||
|
|
||||||
def get_stack_by_id(self, stack_id: int):
|
def get_stack_by_id(self, stack_id: int):
|
||||||
|
"""
|
||||||
|
Get stack by id
|
||||||
|
|
||||||
|
Args:
|
||||||
|
stack_id (int): stack id
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: stack info
|
||||||
|
"""
|
||||||
response = self.portainer.get_stack_by_id(stack_id)
|
response = self.portainer.get_stack_by_id(stack_id)
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
return response.json()
|
return response.json()
|
||||||
else:
|
else:
|
||||||
logger.error(f"Error get stack by id from portainer: {response.text}")
|
logger.error(f"Get stack by id:{stack_id} error: {response.status_code}:{response.text}")
|
||||||
raise CustomException()
|
raise CustomException()
|
||||||
|
|
||||||
def get_stack_by_name(self, stack_name: str, endpoint_id: int):
|
def get_stack_by_name(self, stack_name: str, endpoint_id: int):
|
||||||
|
@ -152,37 +225,60 @@ class PortainerManager:
|
||||||
return stack
|
return stack
|
||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
logger.error(f"Error get stack by name from portainer: {response.text}")
|
logger.error(f"Get stack by name:{stack_name} error: {response.status_code}:{response.text}")
|
||||||
raise CustomException()
|
raise CustomException()
|
||||||
|
|
||||||
def remove_stack(self, stack_id: int, endpoint_id: int):
|
def remove_stack(self, stack_id: int, endpoint_id: int):
|
||||||
|
"""
|
||||||
|
Remove stack by id
|
||||||
|
|
||||||
|
Args:
|
||||||
|
stack_id (int): stack id
|
||||||
|
endpoint_id (int): endpoint id
|
||||||
|
"""
|
||||||
response = self.portainer.remove_stack(stack_id, endpoint_id)
|
response = self.portainer.remove_stack(stack_id, endpoint_id)
|
||||||
if response.status_code != 204:
|
if response.status_code != 204:
|
||||||
logger.error(f"Error remove stack from portainer: {response.text}")
|
logger.error(f"Remove stack:{stack_id} error: {response.status_code}:{response.text}")
|
||||||
raise CustomException()
|
raise CustomException()
|
||||||
|
|
||||||
|
def remove_vloumes(self, stack_name: str, endpoint_id: int):
|
||||||
|
volumes = self.get_volumes_by_stack_name(stack_name, endpoint_id,True)
|
||||||
|
if volumes is not None:
|
||||||
|
volume_names = []
|
||||||
|
for volume in volumes.get("mountpoint", []):
|
||||||
|
volume_names.append(volume["name"])
|
||||||
|
self.remove_volume(volume_names, endpoint_id)
|
||||||
|
|
||||||
def remove_stack_and_volumes(self, stack_id: int, endpoint_id: int):
|
def remove_stack_and_volumes(self, stack_id: int, endpoint_id: int):
|
||||||
|
"""
|
||||||
|
Remove stack and volumes by id
|
||||||
|
|
||||||
|
Args:
|
||||||
|
stack_id (int): stack id
|
||||||
|
endpoint_id (int): endpoint id
|
||||||
|
"""
|
||||||
# get stack name
|
# get stack name
|
||||||
stack_name = self.get_stack_by_id(stack_id).get("Name")
|
stack_name = self.get_stack_by_id(stack_id).get("Name")
|
||||||
|
|
||||||
# remove stack
|
# remove stack
|
||||||
response = self.portainer.remove_stack(stack_id, endpoint_id)
|
response = self.portainer.remove_stack(stack_id, endpoint_id)
|
||||||
if response.status_code != 204:
|
if response.status_code != 204:
|
||||||
logger.error(f"Error remove stack from portainer: {response.text}")
|
logger.error(f"Remove stack:{stack_id} error: {response.status_code}:{response.text}")
|
||||||
raise CustomException()
|
raise CustomException()
|
||||||
|
|
||||||
# remove volumes
|
# remove volumes
|
||||||
try:
|
try:
|
||||||
if stack_name is not None:
|
if stack_name is not None:
|
||||||
volumes = self.get_volumes_by_stack_name(stack_name, endpoint_id,True)
|
volumes = self.get_volumes_by_stack_name(stack_name, endpoint_id,True)
|
||||||
volume_names = []
|
volume_names = []
|
||||||
for volume in volumes.get("mountpoint", []):
|
for volume in volumes:
|
||||||
volume_names.append(volume["name"])
|
volume_names.append(volume["Name"])
|
||||||
self.remove_volume(volume_names, endpoint_id)
|
|
||||||
|
if len(volume_names) > 0:
|
||||||
|
self.remove_volume(volume_names, endpoint_id)
|
||||||
except (CustomException,Exception) as e:
|
except (CustomException,Exception) as e:
|
||||||
raise CustomException()
|
raise CustomException()
|
||||||
|
|
||||||
|
|
||||||
def get_volumes_by_stack_name(self, stack_name: str, endpoint_id: int,dangling:bool):
|
def get_volumes_by_stack_name(self, stack_name: str, endpoint_id: int,dangling:bool):
|
||||||
"""
|
"""
|
||||||
Get volumes by stack name
|
Get volumes by stack name
|
||||||
|
@ -190,32 +286,22 @@ class PortainerManager:
|
||||||
Args:
|
Args:
|
||||||
stack_name (str): stack name
|
stack_name (str): stack name
|
||||||
endpoint_id (int): endpoint id
|
endpoint_id (int): endpoint id
|
||||||
|
dangling (bool): the volume is dangling or not
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
dict: volumes info
|
dict: volumes info
|
||||||
"""
|
"""
|
||||||
response = self.portainer.get_volumes(endpoint_id,dangling)
|
response = self.portainer.get_volumes(endpoint_id,dangling)
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
volumes = response.json().get("Volumes", [])
|
try:
|
||||||
mountpoints = []
|
volumes = response.json().get("Volumes", [])
|
||||||
|
volumes_info = [volume for volume in volumes if volume.get("Labels", {}).get("com.docker.compose.project") == stack_name]
|
||||||
for volume in volumes:
|
except Exception as e:
|
||||||
labels = volume.get("Labels", {})
|
logger.error(f"Get volumes by stack name:{stack_name} error: {e}")
|
||||||
|
raise CustomException()
|
||||||
if labels.get("com.docker.compose.project") == stack_name:
|
return volumes_info
|
||||||
mountpoint_info = {
|
|
||||||
"name": volume["Name"],
|
|
||||||
"path": volume["Mountpoint"]
|
|
||||||
}
|
|
||||||
|
|
||||||
mountpoints.append(mountpoint_info)
|
|
||||||
|
|
||||||
return {
|
|
||||||
"stack_name": stack_name,
|
|
||||||
"mountpoint": mountpoints
|
|
||||||
}
|
|
||||||
else:
|
else:
|
||||||
logger.error(f"Error remove stack from portainer: {response.text}")
|
logger.error(f"Get volumes by stack name:{stack_name} error: {response.status_code}:{response.text}")
|
||||||
raise CustomException()
|
raise CustomException()
|
||||||
|
|
||||||
def remove_volume(self, volume_names: list, endpoint_id: int):
|
def remove_volume(self, volume_names: list, endpoint_id: int):
|
||||||
|
@ -229,5 +315,188 @@ class PortainerManager:
|
||||||
for volume_name in volume_names:
|
for volume_name in volume_names:
|
||||||
response = self.portainer.remove_volume_by_name(endpoint_id,volume_name)
|
response = self.portainer.remove_volume_by_name(endpoint_id,volume_name)
|
||||||
if response.status_code != 204:
|
if response.status_code != 204:
|
||||||
logger.error(f"Error remove volume from portainer: {response.text}")
|
logger.error(f"Remove volume:{volume_name} error: {response.status_code}:{response.text}")
|
||||||
raise CustomException()
|
raise CustomException()
|
||||||
|
|
||||||
|
def up_stack(self, stack_id: int, endpoint_id: int):
|
||||||
|
"""
|
||||||
|
Up stack by id
|
||||||
|
|
||||||
|
Args:
|
||||||
|
stack_id (int): stack id
|
||||||
|
endpoint_id (int): endpoint id
|
||||||
|
"""
|
||||||
|
response = self.portainer.up_stack(stack_id, endpoint_id)
|
||||||
|
if response.status_code == 409:
|
||||||
|
raise CustomException(400,"Invalid Request","The app is already running")
|
||||||
|
elif response.status_code != 200:
|
||||||
|
logger.error(f"Up stack:{stack_id} error: {response.status_code}:{response.text}")
|
||||||
|
raise CustomException()
|
||||||
|
|
||||||
|
def down_stack(self, stack_id: int, endpoint_id: int):
|
||||||
|
"""
|
||||||
|
Down stack by id
|
||||||
|
|
||||||
|
Args:
|
||||||
|
stack_id (int): stack id
|
||||||
|
endpoint_id (int): endpoint id
|
||||||
|
"""
|
||||||
|
response = self.portainer.down_stack(stack_id, endpoint_id)
|
||||||
|
if response.status_code == 400:
|
||||||
|
raise CustomException(400,"Invalid Request","The app is already uninstalled")
|
||||||
|
elif response.status_code != 200:
|
||||||
|
logger.error(f"Down stack:{stack_id} error: {response.status_code}:{response.text}")
|
||||||
|
raise CustomException()
|
||||||
|
|
||||||
|
def stop_stack(self, stack_name: int, endpoint_id: int):
|
||||||
|
"""
|
||||||
|
Stop stack by name
|
||||||
|
|
||||||
|
Args:
|
||||||
|
stack_name (int): stack name
|
||||||
|
endpoint_id (int): endpoint id
|
||||||
|
"""
|
||||||
|
containers_response = self.portainer.get_containers_by_stackName(endpoint_id,stack_name)
|
||||||
|
if containers_response.status_code == 200:
|
||||||
|
containers = containers_response.json()
|
||||||
|
for container in containers:
|
||||||
|
container_id = container.get("Id")
|
||||||
|
stop_response = self.portainer.stop_container(endpoint_id,container_id)
|
||||||
|
if stop_response.status_code in {304, 404}:
|
||||||
|
continue
|
||||||
|
elif stop_response.status_code != 204:
|
||||||
|
message = stop_response.text
|
||||||
|
if message:
|
||||||
|
try:
|
||||||
|
response_details = json.loads(stop_response.text)
|
||||||
|
message = response_details.get('details', 'unknown error')
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
pass
|
||||||
|
logger.error(f"Stop container:{container_id} error: {stop_response.status_code}:{message}")
|
||||||
|
raise CustomException(
|
||||||
|
status_code=400,
|
||||||
|
message="Invalid Request",
|
||||||
|
details=message
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
logger.error(f"Get containers by stack name:{stack_name} error: {containers_response.status_code}:{containers_response.text}")
|
||||||
|
raise CustomException()
|
||||||
|
|
||||||
|
def start_stack(self, stack_name: int, endpoint_id: int):
|
||||||
|
"""
|
||||||
|
Start stack by name
|
||||||
|
|
||||||
|
Args:
|
||||||
|
stack_name (int): stack name
|
||||||
|
endpoint_id (int): endpoint id
|
||||||
|
"""
|
||||||
|
containers_response = self.portainer.get_containers_by_stackName(endpoint_id,stack_name)
|
||||||
|
if containers_response.status_code == 200:
|
||||||
|
containers = containers_response.json()
|
||||||
|
for container in containers:
|
||||||
|
container_id = container.get("Id")
|
||||||
|
start_response=self.portainer.start_container(endpoint_id,container_id)
|
||||||
|
if start_response.status_code in {304, 404}:
|
||||||
|
continue
|
||||||
|
elif start_response.status_code != 204:
|
||||||
|
message = start_response.text
|
||||||
|
if message:
|
||||||
|
try:
|
||||||
|
response_details = json.loads(start_response.text)
|
||||||
|
message = response_details.get('details', 'unknown error')
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
pass
|
||||||
|
logger.error(f"Start container:{container_id} error: {start_response.status_code}:{message}")
|
||||||
|
raise CustomException(
|
||||||
|
status_code=400,
|
||||||
|
message="Invalid Request",
|
||||||
|
details=message
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
logger.error(f"Get containers by stack name:{stack_name} error: {containers_response.status_code}:{containers_response.text}")
|
||||||
|
raise CustomException()
|
||||||
|
|
||||||
|
def restart_stack(self, stack_name: int, endpoint_id: int):
|
||||||
|
"""
|
||||||
|
Restart stack by name
|
||||||
|
|
||||||
|
Args:
|
||||||
|
stack_name (int): stack name
|
||||||
|
endpoint_id (int): endpoint id
|
||||||
|
"""
|
||||||
|
containers_response = self.portainer.get_containers_by_stackName(endpoint_id,stack_name)
|
||||||
|
if containers_response.status_code == 200:
|
||||||
|
containers = containers_response.json()
|
||||||
|
for container in containers:
|
||||||
|
container_id = container.get("Id")
|
||||||
|
restart_response=self.portainer.restart_container(endpoint_id,container_id)
|
||||||
|
if restart_response.status_code != 204:
|
||||||
|
message = restart_response.text
|
||||||
|
if message:
|
||||||
|
try:
|
||||||
|
response_details = json.loads(restart_response.text)
|
||||||
|
message = response_details.get('details', 'unknown error')
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
pass
|
||||||
|
logger.error(f"Restart container:{container_id} error: {restart_response.status_code}:{message}")
|
||||||
|
raise CustomException(
|
||||||
|
status_code=400,
|
||||||
|
message="Invalid Request",
|
||||||
|
details=message
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
logger.error(f"Get containers by stack name:{stack_name} error: {containers_response.status_code}:{containers_response.text}")
|
||||||
|
raise CustomException()
|
||||||
|
|
||||||
|
def get_containers(self, endpoint_id: int):
|
||||||
|
"""
|
||||||
|
Get containers
|
||||||
|
|
||||||
|
Args:
|
||||||
|
endpoint_id (int): endpoint id
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list: containers info
|
||||||
|
"""
|
||||||
|
response = self.portainer.get_containers(endpoint_id)
|
||||||
|
if response.status_code == 200:
|
||||||
|
return response.json()
|
||||||
|
else:
|
||||||
|
logger.error(f"Get containers from endpoint:{endpoint_id} error: {response.status_code}:{response.text}")
|
||||||
|
raise CustomException()
|
||||||
|
|
||||||
|
def get_containers_by_stack_name(self, stack_name: str, endpoint_id: int):
|
||||||
|
"""
|
||||||
|
Get containers by stack name
|
||||||
|
|
||||||
|
Args:
|
||||||
|
stack_name (str): stack name
|
||||||
|
endpoint_id (int): endpoint id
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list: containers info
|
||||||
|
"""
|
||||||
|
response = self.portainer.get_containers_by_stackName(endpoint_id,stack_name)
|
||||||
|
if response.status_code == 200:
|
||||||
|
return response.json()
|
||||||
|
else:
|
||||||
|
logger.error(f"Get containers by stack name:{stack_name} error: {response.status_code}:{response.text}")
|
||||||
|
raise CustomException()
|
||||||
|
|
||||||
|
def get_container_by_id(self, endpoint_id: int, container_id: str):
|
||||||
|
"""
|
||||||
|
Get container by id
|
||||||
|
|
||||||
|
Args:
|
||||||
|
endpoint_id (int): endpoint id
|
||||||
|
container_id (str): container id
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: container info
|
||||||
|
"""
|
||||||
|
response = self.portainer.get_container_by_id(endpoint_id, container_id)
|
||||||
|
if response.status_code == 200:
|
||||||
|
return response.json()
|
||||||
|
else:
|
||||||
|
logger.error(f"Get container by id:{container_id} error: {response.status_code}:{response.text}")
|
||||||
|
raise CustomException()
|
|
@ -9,6 +9,9 @@ from src.external.nginx_proxy_manager_api import NginxProxyManagerAPI
|
||||||
|
|
||||||
|
|
||||||
class ProxyManager:
|
class ProxyManager:
|
||||||
|
"""
|
||||||
|
Proxy Manager
|
||||||
|
"""
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
"""
|
"""
|
||||||
Initialize the ProxyManager instance.
|
Initialize the ProxyManager instance.
|
||||||
|
@ -88,9 +91,9 @@ class ProxyManager:
|
||||||
details=f"matching_domains:{matching_domains} already used"
|
details=f"matching_domains:{matching_domains} already used"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
|
logger.error(f"Check proxy host:{domain_names} exists error:{response.status_code}:{response.text}")
|
||||||
raise CustomException()
|
raise CustomException()
|
||||||
|
|
||||||
|
|
||||||
def create_proxy_for_app(self,domain_names: list[str],forward_host: str,forward_port: int,advanced_config: str = "",forward_scheme: str = "http"):
|
def create_proxy_for_app(self,domain_names: list[str],forward_host: str,forward_port: int,advanced_config: str = "",forward_scheme: str = "http"):
|
||||||
response = self.nginx.create_proxy_host(
|
response = self.nginx.create_proxy_host(
|
||||||
domain_names=domain_names,
|
domain_names=domain_names,
|
||||||
|
@ -100,10 +103,9 @@ class ProxyManager:
|
||||||
advanced_config=advanced_config,
|
advanced_config=advanced_config,
|
||||||
)
|
)
|
||||||
if response.status_code != 201:
|
if response.status_code != 201:
|
||||||
logger.error(f"Error create proxy for app:{response.text}")
|
logger.error(f"Create proxy for app:{forward_host} error:{response.status_code}:{response.text}")
|
||||||
raise CustomException()
|
raise CustomException()
|
||||||
|
|
||||||
|
|
||||||
def update_proxy_for_app(self,domain_names: list[str],forward_host: str,forward_port: int,advanced_config: str = "",forward_scheme: str = "http"):
|
def update_proxy_for_app(self,domain_names: list[str],forward_host: str,forward_port: int,advanced_config: str = "",forward_scheme: str = "http"):
|
||||||
response = self.nginx.update_proxy_host(
|
response = self.nginx.update_proxy_host(
|
||||||
domain_names=domain_names,
|
domain_names=domain_names,
|
||||||
|
@ -113,5 +115,31 @@ class ProxyManager:
|
||||||
advanced_config=advanced_config,
|
advanced_config=advanced_config,
|
||||||
)
|
)
|
||||||
if response.status_code != 200:
|
if response.status_code != 200:
|
||||||
logger.error(f"Error update proxy for app:{response.text}")
|
logger.error(f"Update proxy for app:{forward_host} error:{response.status_code}:{response.text}")
|
||||||
raise CustomException()
|
raise CustomException()
|
||||||
|
|
||||||
|
def get_proxy_host_by_app(self,app_id:str):
|
||||||
|
response = self.nginx.get_proxy_hosts()
|
||||||
|
if response.status_code == 200:
|
||||||
|
proxys_host = response.json()
|
||||||
|
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)
|
||||||
|
return proxy_result
|
||||||
|
else:
|
||||||
|
logger.error(f"Get proxy host by app:{app_id} error:{response.status_code}:{response.text}")
|
||||||
|
raise CustomException()
|
||||||
|
|
||||||
|
def remove_proxy_host_for_app(self,app_id:str):
|
||||||
|
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"))
|
||||||
|
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}")
|
||||||
|
raise CustomException()
|
|
@ -1,3 +1,7 @@
|
||||||
|
from src.core.exception import CustomException
|
||||||
|
from src.core.logger import logger
|
||||||
|
|
||||||
|
|
||||||
class FileHelper:
|
class FileHelper:
|
||||||
"""
|
"""
|
||||||
Helper class for file operations.
|
Helper class for file operations.
|
||||||
|
@ -17,11 +21,13 @@ class FileHelper:
|
||||||
Returns:
|
Returns:
|
||||||
str: The contents of the file.
|
str: The contents of the file.
|
||||||
"""
|
"""
|
||||||
|
try:
|
||||||
with open(file_path, 'r') as f:
|
with open(file_path, 'r') as f:
|
||||||
content = f.read()
|
content = f.read()
|
||||||
|
return content
|
||||||
return content
|
except:
|
||||||
|
logger.error(f"Failed to read file {file_path}")
|
||||||
|
raise CustomException()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def write_file(file_path, content):
|
def write_file(file_path, content):
|
||||||
|
@ -32,6 +38,9 @@ class FileHelper:
|
||||||
file_path (str): The path to the file.
|
file_path (str): The path to the file.
|
||||||
content (str): The content to be written.
|
content (str): The content to be written.
|
||||||
"""
|
"""
|
||||||
|
try:
|
||||||
with open(file_path, 'w') as f:
|
with open(file_path, 'w') as f:
|
||||||
f.write(content)
|
f.write(content)
|
||||||
|
except:
|
||||||
|
logger.error(f"Failed to write file {file_path}")
|
||||||
|
raise CustomException()
|
Loading…
Reference in a new issue