This commit is contained in:
zhaojing1987 2023-10-09 15:01:18 +08:00
parent 62eee2de82
commit eff1da37ae
33 changed files with 735 additions and 159 deletions

View file

@ -1,6 +1,6 @@
# run app : uvicorn src.main:app --reload --port 9999 --log-level error ## 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
## supervisorctl update ## supervisorctl update
## supervisorctl status ## supervisorctl status

View file

@ -6,7 +6,7 @@ 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()
@router.get( @router.get(
"/apps/catalog/{locale}", "/apps/catalog/{locale}",
@ -23,7 +23,6 @@ def get_catalog_apps(
): ):
return AppManger().get_catalog_apps(locale) return AppManger().get_catalog_apps(locale)
@router.get( @router.get(
"/apps/available/{locale}", "/apps/available/{locale}",
summary="List Available Apps", summary="List Available Apps",

View file

@ -1,12 +1,94 @@
from fastapi import APIRouter, Query
from typing import List, Optional from typing import List, Optional
from fastapi import APIRouter, Query,Path
from fastapi.params import Body
from src.schemas.domainNames import DomainNames
from src.schemas.errorResponse import ErrorResponse
from src.schemas.proxyHosts import ProxyHost
from src.services.app_manager import AppManger
router = APIRouter() router = APIRouter()
@router.get("/proxys".format(),summary="Get proxys",description="Get proxys") @router.get(
def get_proxys(): "/proxys/{app_id}",
return {"proxys": "proxys"} summary="Get Proxys",
description="Get proxys by app",
responses={
200: {"model": list[ProxyHost]},
400: {"model": ErrorResponse},
500: {"model": ErrorResponse},
}
)
def get_proxys(
app_id: str = Path(..., description="App ID to get proxys from"),
endpointId: int = Query(None, description="Endpoint ID to get proxys from. If not set, get proxys from the local endpoint")
):
return AppManger().get_proxys_by_app(app_id,endpointId)
@router.put("/proxys") @router.post(
def update_settings(): "/proxys/{app_id}",
return {"proxys": "proxys"} summary="Create Proxy",
description="Create a proxy host",
responses={
200: {"model": list[ProxyHost]},
400: {"model": ErrorResponse},
500: {"model": ErrorResponse},
}
)
def create_proxys(
domain_names: DomainNames = Body(..., description="Domain names to create proxys from", example={"domain_names": ["example1.com","example2.com"]}),
app_id: str = Path(..., description="App ID to create proxys from"),
endpointId: int = Query(None, description="Endpoint ID to create proxys from. If not set, create proxys from the local endpoint"),
):
return AppManger().create_proxy_by_app(app_id,domain_names.domain_names,endpointId)
@router.put(
"/proxys/{app_id}}",
summary="Update Proxys",
description="Update proxys by app",
responses={
200: {"model": list[ProxyHost]},
400: {"model": ErrorResponse},
500: {"model": ErrorResponse},
}
)
def update_proxys(
proxyHost:ProxyHost = Body(..., description="Proxy host to update proxys from"),
app_id: str = Path(..., description="App ID to create proxys from"),
endpointId: int = Query(None, description="Endpoint ID to create proxys from. If not set, create proxys from the local endpoint"),
):
return AppManger().update_proxy_by_app(app_id,proxyHost,endpointId)
@router.delete(
"/proxys/app/{app_id}",
summary="Delete Proxys",
description="Delete proxys by app",
status_code=204,
responses={
204: {"description": "Delete Proxys Success"},
400: {"model": ErrorResponse},
500: {"model": ErrorResponse},
}
)
def delete_proxys_by_app(
app_id: str = Path(..., description="App ID to create proxys from"),
endpointId: int = Query(None, description="Endpoint ID to create proxys from. If not set, create proxys from the local endpoint"),
):
AppManger().remove_proxy_by_app(app_id,endpointId)
@router.delete(
"/proxys/{proxy_id}",
summary="Delete Proxys",
description="Delete proxys by proxy_id",
status_code=204,
responses={
204: {"description": "Delete Proxys Success"},
400: {"model": ErrorResponse},
500: {"model": ErrorResponse},
}
)
def delete_proxys_by_id(
proxy_id: int = Path(..., description="Proxy ID to delete proxys from")
):
AppManger().remove_proxy_by_id(proxy_id)

View file

@ -1,11 +1,22 @@
from fastapi import APIRouter, Query from fastapi import APIRouter, Query
from typing import List, Optional from src.schemas.appSettings import AppSettings
from src.schemas.errorResponse import ErrorResponse
from src.services.settings_manager import SettingsManager
router = APIRouter() router = APIRouter()
@router.get("/settings",summary="Get settings",description="Get settings") @router.get("/settings",
summary="Get settings",
description="Get settings",
responses={
200: {"model": AppSettings},
400: {"model": ErrorResponse},
500: {"model": ErrorResponse},
}
)
def get_settings(): def get_settings():
return {"settings": "settings"} return SettingsManager().read_all()
@router.put("/settings") @router.put("/settings")
def update_settings(): def update_settings():

View file

@ -1,25 +1,18 @@
# The config for appmanage
[appmanage]
access_token =
# The config for nginx proxy manager # The config for nginx proxy manager
[nginx_proxy_manager] [nginx_proxy_manager]
base_url = http://websoft9-proxy:81/api base_url = http://websoft9-proxy:81/api
#base_url = http://47.92.222.186/w9proxy/api
user_name = help@websoft9.com user_name = help@websoft9.com
user_pwd = ECTKPRAWhij789yr 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
user_name = websoft9 user_name = websoft9
user_pwd = Rk9qOQ68Inf0 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
user_name = admin user_name = admin
user_pwd = ]}fU;XmVH].VI{Hh user_pwd = ]}fU;XmVH].VI{Hh
@ -28,19 +21,4 @@ user_pwd = ]}fU;XmVH].VI{Hh
path = /websoft9/library/apps path = /websoft9/library/apps
[app_media] [app_media]
path = /websoft9/media/json/ 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]
url_list = https://api.ipify.org/,
https://icanhazip.com/,
http://ifconfig.co/,
https://ident.me/,
https://ifconfig.me/,
https://ipecho.net/plain,
https://ipinfo.io/ip,
https://ip.sb/,
http://whatismyip.akamai.com/,
https://inet-ip.info/,
http://bot.whatismyipaddress.com/

View file

@ -1,5 +1,6 @@
import os import os
import configparser import configparser
from src.core.logger import logger
class ConfigManager: class ConfigManager:

View file

@ -2,12 +2,14 @@ import logging
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
from fastapi.staticfiles import StaticFiles
from src.api.v1.routers import app as api_app from src.api.v1.routers import app as api_app
from src.api.v1.routers import settings as api_settings from src.api.v1.routers import settings as api_settings
from src.api.v1.routers import proxy as api_proxy from src.api.v1.routers import proxy as api_proxy
from src.core.exception import CustomException 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
from fastapi.responses import HTMLResponse
uvicorn_logger = logging.getLogger("uvicorn") uvicorn_logger = logging.getLogger("uvicorn")
@ -23,8 +25,35 @@ app = FastAPI(
# summary="[ Base URL: /api/v1 ]", # summary="[ Base URL: /api/v1 ]",
description="This documentation describes the AppManage API.", description="This documentation describes the AppManage API.",
version="0.0.1", version="0.0.1",
docs_url=None
) )
app.mount("/static", StaticFiles(directory="swagger-ui"), name="static")
@app.get("/docs", response_class=HTMLResponse,include_in_schema=False)
async def custom_swagger_ui_html():
return """
<!DOCTYPE html>
<html>
<head>
<title>Websoft9 API</title>
<link rel="stylesheet" type="text/css" href="/static/swagger-ui.css">
<script src="/static/swagger-ui-bundle.js"></script>
</head>
<body>
<div id="swagger-ui"></div>
<script>
const ui = SwaggerUIBundle({
url: "/openapi.json",
dom_id: '#swagger-ui',
presets: [SwaggerUIBundle.presets.apis, SwaggerUIBundle.SwaggerUIStandalonePreset],
layout: "BaseLayout"
})
</script>
</body>
</html>
"""
# remove 422 responses # remove 422 responses
@app.on_event("startup") @app.on_event("startup")
async def remove_422_responses(): async def remove_422_responses():

View file

@ -0,0 +1,29 @@
from pydantic import BaseModel, Field, HttpUrl
class NginxProxyManagerSetting(BaseModel):
base_url: HttpUrl = Field(..., title="The base url for nginx proxy manager")
user_name: str = Field(..., title="The user name for nginx proxy manager")
user_pwd: str = Field(..., title="The user password for nginx proxy manager")
class GiteaSetting(BaseModel):
base_url: HttpUrl = Field(..., title="The base url for gitea")
user_name: str = Field(..., title="The user name for gitea")
user_pwd: str = Field(..., title="The user password for gitea")
class PortainerSetting(BaseModel):
base_url: HttpUrl = Field(..., title="The base url for portainer")
user_name: str = Field(..., title="The user name for portainer")
user_pwd: str = Field(..., title="The user password for portainer")
class DockerLibrarySetting(BaseModel):
path: str = Field(..., title="The path of docker library")
class AppMediaSetting(BaseModel):
path: str = Field(..., title="The path of app media")
class AppSettings(BaseModel):
nginx_proxy_manager: NginxProxyManagerSetting
gitea: GiteaSetting
portainer: PortainerSetting
docker_library: DockerLibrarySetting
app_media: AppMediaSetting

View file

@ -0,0 +1,24 @@
from typing import Optional, List
from pydantic import BaseModel, Field, validator
from src.core.exception import CustomException
class DomainNames(BaseModel):
domain_names: List[str]
@validator('domain_names', each_item=True)
def validate_domain_name(cls, v):
if not v.strip():
raise CustomException(400,"Invalid Request","domain_names' cannot be empty string.")
if v.startswith('http://') or v.startswith('https://'):
raise CustomException(400,"Invalid Request","'domain_names' cannot start with 'http://' or 'https://'.")
return v
@validator('domain_names')
def validate_domain_names(cls, v):
if not v:
raise CustomException(400,"Invalid Request","domain_names' cannot be empty.")
if len(set(v)) != len(v):
raise CustomException(400,"Invalid Request","Duplicate entries found in 'domain_names'. All domains must be unique.")
return v

View file

@ -0,0 +1,26 @@
from typing import List
from pydantic import BaseModel
from typing import List
from pydantic import BaseModel, validator
from src.core.exception import CustomException
class ProxyHost(BaseModel):
proxy_id: int
domain_names: List[str]
@validator('domain_names', each_item=True)
def validate_domain_name(cls, v):
if not v.strip():
raise CustomException(400,"Invalid Request","domain_names' cannot be empty string.")
if v.startswith('http://') or v.startswith('https://'):
raise CustomException(400,"Invalid Request","'domain_names' cannot start with 'http://' or 'https://'.")
return v
@validator('domain_names')
def validate_domain_names(cls, v):
if not v:
raise CustomException(400,"Invalid Request","domain_names' cannot be empty.")
if len(set(v)) != len(v):
raise CustomException(400,"Invalid Request","Duplicate entries found in 'domain_names'. All domains must be unique.")
return v

View file

@ -8,6 +8,8 @@ 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.schemas.appResponse import AppResponse
from src.schemas.proxyHosts import ProxyHost
from src.services.common_check import check_appId, check_appName_and_appVersion, check_domain_names, check_endpointId
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
@ -60,7 +62,7 @@ class AppManger:
portainerManager = PortainerManager() portainerManager = PortainerManager()
# Check the endpointId is exists. # Check the endpointId is exists.
endpointId = self._check_endpointId(endpointId, portainerManager) endpointId = check_endpointId(endpointId, portainerManager)
try: try:
apps_info = [] apps_info = []
@ -105,7 +107,7 @@ class AppManger:
portainerManager = PortainerManager() portainerManager = PortainerManager()
# Check the endpointId is exists. # Check the endpointId is exists.
endpointId = self._check_endpointId(endpointId, portainerManager) endpointId = check_endpointId(endpointId, portainerManager)
# validate the app_id is exists in portainer # validate the app_id is exists in portainer
is_stack_exists = portainerManager.check_stack_exists(app_id,endpointId) is_stack_exists = portainerManager.check_stack_exists(app_id,endpointId)
@ -234,22 +236,22 @@ class AppManger:
giteaManager = GiteaManager() giteaManager = GiteaManager()
# Check the endpointId is exists. # Check the endpointId is exists.
endpointId = self._check_endpointId(endpointId, portainerManager) endpointId = 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
app_version = appInstall.edition.version app_version = appInstall.edition.version
self._check_appName_and_appVersion(app_name,app_version,library_path) check_appName_and_appVersion(app_name,app_version,library_path)
# validate the app_id # validate the app_id
app_id = appInstall.app_id app_id = appInstall.app_id
self._check_appId(app_id,endpointId,giteaManager,portainerManager) check_appId(app_id,endpointId,giteaManager,portainerManager)
# validate the domain_names # validate the domain_names
proxy_enabled = appInstall.proxy_enabled proxy_enabled = appInstall.proxy_enabled
domain_names = appInstall.domain_names domain_names = appInstall.domain_names
if proxy_enabled: if proxy_enabled:
self._check_domain_names(domain_names) check_domain_names(domain_names)
# Install app - Step 1 : create repo in gitea # Install app - Step 1 : create repo in gitea
repo_url = giteaManager.create_repo(app_id) repo_url = giteaManager.create_repo(app_id)
@ -317,10 +319,10 @@ class AppManger:
if os.path.exists(nginx_proxy_path): if os.path.exists(nginx_proxy_path):
# Get the advanced config # Get the advanced config
advanced_config = FileHelper.read_file(nginx_proxy_path) advanced_config = FileHelper.read_file(nginx_proxy_path)
ProxyManager().create_proxy_for_app(domain_names,app_id,forward_port,advanced_config) ProxyManager().create_proxy_by_app(domain_names,app_id,forward_port,advanced_config)
else: else:
ProxyManager().create_proxy_for_app(domain_names,app_id,forward_port) ProxyManager().create_proxy_by_app(domain_names,app_id,forward_port)
except (CustomException,Exception) as e: 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
@ -336,7 +338,7 @@ class AppManger:
portainerManager = PortainerManager() portainerManager = PortainerManager()
# Check the endpointId is exists. # Check the endpointId is exists.
endpointId = self._check_endpointId(endpointId, portainerManager) endpointId = check_endpointId(endpointId, portainerManager)
# validate the app_id is exists in portainer # validate the app_id is exists in portainer
is_stack_exists = portainerManager.check_stack_exists(app_id,endpointId) is_stack_exists = portainerManager.check_stack_exists(app_id,endpointId)
@ -364,7 +366,7 @@ class AppManger:
portainerManager = PortainerManager() portainerManager = PortainerManager()
# Check the endpointId is exists. # Check the endpointId is exists.
endpointId = self._check_endpointId(endpointId, portainerManager) endpointId = check_endpointId(endpointId, portainerManager)
# validate the app_id is exists in portainer # validate the app_id is exists in portainer
is_stack_exists = portainerManager.check_stack_exists(app_id,endpointId) is_stack_exists = portainerManager.check_stack_exists(app_id,endpointId)
@ -399,7 +401,7 @@ class AppManger:
proxys_host = proxyManager.get_proxy_host_by_app(app_id) proxys_host = proxyManager.get_proxy_host_by_app(app_id)
# If the proxy is exists, remove it # If the proxy is exists, remove it
if proxys_host: if proxys_host:
proxyManager.remove_proxy_host_for_app(app_id) proxyManager.remove_proxy_host_by_app(app_id)
# Uninstall app - Step 2 : remove repo in gitea # Uninstall app - Step 2 : remove repo in gitea
# Check the repo is exists # Check the repo is exists
@ -427,7 +429,7 @@ class AppManger:
portainerManager = PortainerManager() portainerManager = PortainerManager()
# Check the endpointId is exists. # Check the endpointId is exists.
endpointId = self._check_endpointId(endpointId, portainerManager) endpointId = check_endpointId(endpointId, portainerManager)
# validate the app_id is exists in portainer # validate the app_id is exists in portainer
is_stack_exists = portainerManager.check_stack_exists(app_id,endpointId) is_stack_exists = portainerManager.check_stack_exists(app_id,endpointId)
@ -458,7 +460,7 @@ class AppManger:
proxys_host = proxyManager.get_proxy_host_by_app(app_id) proxys_host = proxyManager.get_proxy_host_by_app(app_id)
# If the proxy is exists, remove it # If the proxy is exists, remove it
if proxys_host: if proxys_host:
proxyManager.remove_proxy_host_for_app(app_id) proxyManager.remove_proxy_host_by_app(app_id)
# Check the repo is exists # Check the repo is exists
giteaManager = GiteaManager() giteaManager = GiteaManager()
@ -472,7 +474,7 @@ class AppManger:
portainerManager = PortainerManager() portainerManager = PortainerManager()
# Check the endpointId is exists. # Check the endpointId is exists.
endpointId = self._check_endpointId(endpointId, portainerManager) endpointId = check_endpointId(endpointId, portainerManager)
# validate the app_id is exists in portainer # validate the app_id is exists in portainer
# is_stack_exists = portainerManager.check_stack_exists(app_id,endpointId) # is_stack_exists = portainerManager.check_stack_exists(app_id,endpointId)
@ -503,7 +505,7 @@ class AppManger:
portainerManager = PortainerManager() portainerManager = PortainerManager()
# Check the endpointId is exists. # Check the endpointId is exists.
endpointId = self._check_endpointId(endpointId, portainerManager) endpointId = check_endpointId(endpointId, portainerManager)
# validate the app_id is exists in portainer # validate the app_id is exists in portainer
# is_stack_exists = portainerManager.check_stack_exists(app_id,endpointId) # is_stack_exists = portainerManager.check_stack_exists(app_id,endpointId)
@ -533,7 +535,7 @@ class AppManger:
portainerManager = PortainerManager() portainerManager = PortainerManager()
# Check the endpointId is exists. # Check the endpointId is exists.
endpointId = self._check_endpointId(endpointId, portainerManager) endpointId = check_endpointId(endpointId, portainerManager)
# validate the app_id is exists in portainer # validate the app_id is exists in portainer
# is_stack_exists = portainerManager.check_stack_exists(app_id,endpointId) # is_stack_exists = portainerManager.check_stack_exists(app_id,endpointId)
@ -559,81 +561,164 @@ class AppManger:
) )
portainerManager.restart_stack(app_id,endpointId) portainerManager.restart_stack(app_id,endpointId)
def _check_appName_and_appVersion(self,app_name:str, app_version:str,library_path:str): def get_proxys_by_app(self,app_id:str,endpointId:int = None):
""" portainerManager = PortainerManager()
Check the app_name and app_version is exists in docker library proxyManager = ProxyManager()
Args: # Check the endpointId is exists.
app_name (str): App Name endpointId = check_endpointId(endpointId, portainerManager)
app_version (str): App Version
stack_info = portainerManager.get_stack_by_name(app_id,endpointId)
Raises: if stack_info is None:
CustomException: If the app_name or app_version is not exists in docker library
"""
if not os.path.exists(f"{library_path}/{app_name}"):
logger.error(f"When install app:{app_name}, the app is not exists in docker library")
raise CustomException( raise CustomException(
status_code=400, status_code=400,
message="Invalid Request", message="Invalid Request",
details=f"app_name:{app_name} not supported", 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 inactive, can not get proxy,you can redeploy it"
# )
# Get the proxys
proxys_host = proxyManager.get_proxy_host_by_app(app_id)
return proxys_host
def create_proxy_by_app(self,app_id:str,domain_names:list[str],endpointId:int = None):
proxyManager = ProxyManager()
portainerManager = PortainerManager()
# Check the endpointId is exists.
endpointId = check_endpointId(endpointId, portainerManager)
# Check the app_id is exists
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"
)
# Check the app is active
stack_status = stack_info.get("Status",None)
if stack_status == 2:
raise CustomException(
status_code=400,
message="Invalid Request",
details=f"{app_id} is inactive, can not create proxy,you can redeploy it"
)
# Check the domain_names
check_domain_names(domain_names)
# Get the forward port
stack_env = self.get_app_by_id(app_id,endpointId).env
if stack_env:
for item in stack_env:
key, value = item.split("=", 1)
if key == "APP_HTTP_PORT":
forward_port = value
break
# Create proxy
if forward_port:
proxy_host = proxyManager.create_proxy_by_app(domain_names,app_id,forward_port)
if proxy_host:
return ProxyHost(
proxy_id=proxy_host.get("id"),
domain_names=proxy_host.get("domain_names"),
)
else:
raise CustomException()
else:
raise CustomException()
else:
raise CustomException()
def remove_proxy_by_app(self,app_id:str,endpointId:int = None):
proxyManager = ProxyManager()
portainerManager = PortainerManager()
# Check the endpointId is exists.
endpointId = check_endpointId(endpointId, portainerManager)
# Check the app_id is exists
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"
)
domain_names = proxyManager.get_proxy_host_by_app(app_id)
if not domain_names:
raise CustomException(
status_code=400,
message="Invalid Request",
details=f"{app_id} is not exists proxy"
)
# Remove proxy
proxyManager.remove_proxy_host_by_app(app_id)
def remove_proxy_by_id(self,proxy_id:int):
# Check the proxy id is exists
host = ProxyManager().get_proxy_host_by_id(proxy_id)
if host is None:
raise CustomException(
status_code=400,
message="Invalid Request",
details=f"Proxy ID:{proxy_id} Not Found"
)
ProxyManager().remove_proxy_host_by_id(proxy_id)
def update_proxy_by_app(self,app_id:str,proxyHost:ProxyHost,endpointId:int = None):
proxyManager = ProxyManager()
portainerManager = PortainerManager()
# Check the endpointId is exists.
endpointId = check_endpointId(endpointId, portainerManager)
# Check the app_id is exists
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"
)
# Check the proxy id is exists
host = proxyManager.get_proxy_host_by_id(proxyHost.proxy_id)
if host is None:
raise CustomException(
status_code=400,
message="Invalid Request",
details=f"Proxy ID:{proxyHost.proxy_id} Not Found"
)
# Check the domain_names
# Update proxy
proxy_host = proxyManager.update_proxy_by_app(
proxyHost.proxy_id,
proxyHost.domain_names,
host.get("forward_host"),
host.get("forward_port"),
host.get("advanced_config"),
host.get("forward_scheme")
)
if proxy_host:
return ProxyHost(
proxy_id=proxy_host.get("id"),
domain_names=proxy_host.get("domain_names"),
) )
else: else:
raise CustomException()
with open(f"{library_path}/{app_name}/variables.json", "r") as f:
variables = json.load(f)
community_editions = [d for d in variables["edition"] if d["dist"] == "community"]
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(
status_code=400,
message="Invalid Request",
details=f"app_version:{app_version} not supported",
)
def _check_appId(self,app_id:str,endpointId:int,giteaManager:GiteaManager,portainerManager:PortainerManager):
"""
Check the app_id is exists in gitea and portainer
Args:
app_id (str): App Id
endpointId (int): Endpoint Id
Raises:
CustomException: If the app_id is exists in gitea or portainer
"""
# validate the app_id is exists in gitea
is_repo_exists = giteaManager.check_repo_exists(app_id)
if is_repo_exists:
logger.error(f"When install app,the app_id:{{app_id}} is exists in gitea")
raise CustomException(
status_code=400,
message="Invalid Request",
details=f"App_id:{app_id} is exists in gitea"
)
# validate the app_id is exists in portainer
is_stack_exists = portainerManager.check_stack_exists(app_id,endpointId)
if is_stack_exists:
logger.error(f"When install app, the app_id:{app_id} is exists in portainer")
raise CustomException(
status_code=400,
message="Invalid Request",
details=f"app_id:{app_id} is exists in portainer"
)
def _check_domain_names(self,domain_names:list[str]):
"""
Check the domain_names is exists in proxy
Args:
domain_names (list[str]): Domain Names
Raises:
CustomException: If the domain_names is not exists in proxy
"""
ProxyManager().check_proxy_host_exists(domain_names)
def _init_local_repo_and_push_to_remote(self,local_path:str,repo_url:str): def _init_local_repo_and_push_to_remote(self,local_path:str,repo_url:str):
""" """
@ -653,27 +738,4 @@ class AppManger:
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

View file

@ -0,0 +1,108 @@
import os
import json
from src.core.logger import logger
from src.core.exception import CustomException
from src.services.gitea_manager import GiteaManager
from src.services.portainer_manager import PortainerManager
from src.services.proxy_manager import ProxyManager
def check_appName_and_appVersion(app_name:str, app_version:str,library_path:str):
"""
Check the app_name and app_version is exists in docker library
Args:
app_name (str): App Name
app_version (str): App Version
Raises:
CustomException: If the app_name or app_version is not exists in docker library
"""
if not os.path.exists(f"{library_path}/{app_name}"):
logger.error(f"When install app:{app_name}, the app is not exists in docker library")
raise CustomException(
status_code=400,
message="Invalid Request",
details=f"app_name:{app_name} not supported",
)
else:
with open(f"{library_path}/{app_name}/variables.json", "r") as f:
variables = json.load(f)
community_editions = [d for d in variables["edition"] if d["dist"] == "community"]
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(
status_code=400,
message="Invalid Request",
details=f"app_version:{app_version} not supported",
)
def check_appId(app_id:str,endpointId:int,giteaManager:GiteaManager,portainerManager:PortainerManager):
"""
Check the app_id is exists in gitea and portainer
Args:
app_id (str): App Id
endpointId (int): Endpoint Id
Raises:
CustomException: If the app_id is exists in gitea or portainer
"""
# validate the app_id is exists in gitea
is_repo_exists = giteaManager.check_repo_exists(app_id)
if is_repo_exists:
logger.error(f"When install app,the app_id:{{app_id}} is exists in gitea")
raise CustomException(
status_code=400,
message="Invalid Request",
details=f"App_id:{app_id} is exists in gitea"
)
# validate the app_id is exists in portainer
is_stack_exists = portainerManager.check_stack_exists(app_id,endpointId)
if is_stack_exists:
logger.error(f"When install app, the app_id:{app_id} is exists in portainer")
raise CustomException(
status_code=400,
message="Invalid Request",
details=f"app_id:{app_id} is exists in portainer"
)
def check_domain_names(domain_names:list[str]):
"""
Check the domain_names is exists in proxy
Args:
domain_names (list[str]): Domain Names
Raises:
CustomException: If the domain_names is not exists in proxy
"""
ProxyManager().check_proxy_host_exists(domain_names)
def check_endpointId(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

View file

@ -1,13 +1,12 @@
import json
import time import time
import jwt import jwt
import keyring import keyring
import json
from src.core.config import ConfigManager from src.core.config import ConfigManager
from src.core.exception import CustomException from src.core.exception import CustomException
from src.core.logger import logger from src.core.logger import logger
from src.external.nginx_proxy_manager_api import NginxProxyManagerAPI from src.external.nginx_proxy_manager_api import NginxProxyManagerAPI
class ProxyManager: class ProxyManager:
""" """
Proxy Manager Proxy Manager
@ -87,14 +86,14 @@ class ProxyManager:
if matching_domains: if matching_domains:
raise CustomException( raise CustomException(
status_code=400, status_code=400,
message=f"Proxy Host Already Used", message=f"Invalid Request",
details=f"matching_domains:{matching_domains} already used" details=f"{matching_domains} already used"
) )
else: else:
logger.error(f"Check proxy host:{domain_names} exists error:{response.status_code}:{response.text}") 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_by_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,
forward_scheme=forward_scheme, forward_scheme=forward_scheme,
@ -105,18 +104,30 @@ class ProxyManager:
if response.status_code != 201: if response.status_code != 201:
logger.error(f"Create proxy for app:{forward_host} error:{response.status_code}:{response.text}") logger.error(f"Create proxy for app:{forward_host} error:{response.status_code}:{response.text}")
raise CustomException() raise CustomException()
else:
return response.json()
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_by_app(self,proxy_id:int,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(
proxy_id=proxy_id,
domain_names=domain_names, domain_names=domain_names,
forward_scheme=forward_scheme, forward_scheme=forward_scheme,
forward_host=forward_host, forward_host=forward_host,
forward_port=forward_port, forward_port=forward_port,
advanced_config=advanced_config, advanced_config=advanced_config,
) )
if response.status_code != 200: if response.status_code == 200:
return response.json()
elif response.status_code == 500:
logger.error(f"Update proxy for app:{forward_host} error:{response.status_code}:{response.text}") logger.error(f"Update proxy for app:{forward_host} error:{response.status_code}:{response.text}")
raise CustomException() raise CustomException()
else:
logger.error(f"Update proxy for app:{forward_host} error:{response.status_code}:{response.text}")
raise CustomException(
status_code=400,
message=f"Invalid Request",
details=f"{json.loads(response.text).get('error',{}).get('message')}"
)
def get_proxy_host_by_app(self,app_id:str): def get_proxy_host_by_app(self,app_id:str):
response = self.nginx.get_proxy_hosts() response = self.nginx.get_proxy_hosts()
@ -134,12 +145,37 @@ class ProxyManager:
else: else:
logger.error(f"Get proxy host by app:{app_id} error:{response.status_code}:{response.text}") logger.error(f"Get proxy host by app:{app_id} error:{response.status_code}:{response.text}")
raise CustomException() raise CustomException()
def remove_proxy_host_for_app(self,app_id:str): def remove_proxy_host_by_app(self,app_id:str):
proxy_hosts = self.get_proxy_host_by_app(app_id) proxy_hosts = self.get_proxy_host_by_app(app_id)
if proxy_hosts: if proxy_hosts:
for proxy_host in proxy_hosts: for proxy_host in proxy_hosts:
response = self.nginx.delete_proxy_host(proxy_host.get("proxy_id")) response = self.nginx.delete_proxy_host(proxy_host.get("proxy_id"))
if response.status_code != 200: if response.status_code != 200:
logger.error(f"Remove proxy host:{proxy_host.get('proxy_id')} for app:{app_id} error:{response.status_code}:{response.text}") logger.error(f"Remove proxy host:{proxy_host.get('proxy_id')} for app:{app_id} error:{response.status_code}:{response.text}")
raise CustomException() raise CustomException()
def remove_proxy_host_by_id(self,proxy_id:int):
response = self.nginx.delete_proxy_host(proxy_id)
if response.status_code != 200:
logger.error(f"Remove proxy host:{proxy_id} error:{response.status_code}:{response.text}")
raise CustomException()
def get_proxy_hosts(self):
response = self.nginx.get_proxy_hosts()
if response.status_code == 200:
return response.json()
else:
logger.error(f"Get proxy hosts error:{response.status_code}:{response.text}")
raise CustomException()
def get_proxy_host_by_id(self,proxy_id:int):
proxy_hosts = self.get_proxy_hosts()
try:
for proxy_host in proxy_hosts:
if proxy_host.get("id") == proxy_id:
return proxy_host
return None
except Exception as e:
logger.error(f"Get proxy host by id:{proxy_id} error:{e}")
raise CustomException()

View file

@ -0,0 +1,34 @@
import os
import configparser
from typing import Dict
from src.core.exception import CustomException
from src.core.logger import logger
from src.schemas.appSettings import AppSettings
class SettingsManager:
def __init__(self):
script_dir = os.path.dirname(os.path.realpath(__file__))
config_dir = os.path.join(script_dir, "../config")
self.config_file_path = os.path.join(config_dir, "config.ini")
self.config_file_path = os.path.abspath(self.config_file_path)
self.config = configparser.ConfigParser()
def read_all(self) -> Dict[str, Dict[str, str]]:
try:
self.config.read(self.config_file_path)
data = {s:dict(self.config.items(s)) for s in self.config.sections()}
return AppSettings(**data)
except Exception as e:
logger.error(e)
raise CustomException()
def write_all(self, data: AppSettings):
for section, kv in data.model_dump().items():
if section not in self.config.sections():
self.config.add_section(section)
for key, value in kv.items():
self.config.set(section, key, value)
with open(self.filename, 'w') as configfile:
self.config.write(configfile)

View file

@ -23,7 +23,7 @@ class FileHelper:
""" """
try: 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: except:
logger.error(f"Failed to read file {file_path}") logger.error(f"Failed to read file {file_path}")

Binary file not shown.

After

Width:  |  Height:  |  Size: 665 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 628 B

View file

@ -0,0 +1,16 @@
html {
box-sizing: border-box;
overflow: -moz-scrollbars-vertical;
overflow-y: scroll;
}
*,
*:before,
*:after {
box-sizing: inherit;
}
body {
margin: 0;
background: #fafafa;
}

View file

@ -0,0 +1,19 @@
<!-- HTML for static distribution bundle build -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Swagger UI</title>
<link rel="stylesheet" type="text/css" href="./swagger-ui.css" />
<link rel="stylesheet" type="text/css" href="index.css" />
<link rel="icon" type="image/png" href="./favicon-32x32.png" sizes="32x32" />
<link rel="icon" type="image/png" href="./favicon-16x16.png" sizes="16x16" />
</head>
<body>
<div id="swagger-ui"></div>
<script src="./swagger-ui-bundle.js" charset="UTF-8"> </script>
<script src="./swagger-ui-standalone-preset.js" charset="UTF-8"> </script>
<script src="./swagger-initializer.js" charset="UTF-8"> </script>
</body>
</html>

View file

@ -0,0 +1,79 @@
<!doctype html>
<html lang="en-US">
<head>
<title>Swagger UI: OAuth2 Redirect</title>
</head>
<body>
<script>
'use strict';
function run () {
var oauth2 = window.opener.swaggerUIRedirectOauth2;
var sentState = oauth2.state;
var redirectUrl = oauth2.redirectUrl;
var isValid, qp, arr;
if (/code|token|error/.test(window.location.hash)) {
qp = window.location.hash.substring(1).replace('?', '&');
} else {
qp = location.search.substring(1);
}
arr = qp.split("&");
arr.forEach(function (v,i,_arr) { _arr[i] = '"' + v.replace('=', '":"') + '"';});
qp = qp ? JSON.parse('{' + arr.join() + '}',
function (key, value) {
return key === "" ? value : decodeURIComponent(value);
}
) : {};
isValid = qp.state === sentState;
if ((
oauth2.auth.schema.get("flow") === "accessCode" ||
oauth2.auth.schema.get("flow") === "authorizationCode" ||
oauth2.auth.schema.get("flow") === "authorization_code"
) && !oauth2.auth.code) {
if (!isValid) {
oauth2.errCb({
authId: oauth2.auth.name,
source: "auth",
level: "warning",
message: "Authorization may be unsafe, passed state was changed in server. The passed state wasn't returned from auth server."
});
}
if (qp.code) {
delete oauth2.state;
oauth2.auth.code = qp.code;
oauth2.callback({auth: oauth2.auth, redirectUrl: redirectUrl});
} else {
let oauthErrorMsg;
if (qp.error) {
oauthErrorMsg = "["+qp.error+"]: " +
(qp.error_description ? qp.error_description+ ". " : "no accessCode received from the server. ") +
(qp.error_uri ? "More info: "+qp.error_uri : "");
}
oauth2.errCb({
authId: oauth2.auth.name,
source: "auth",
level: "error",
message: oauthErrorMsg || "[Authorization failed]: no accessCode received from the server."
});
}
} else {
oauth2.callback({auth: oauth2.auth, token: qp, isValid: isValid, redirectUrl: redirectUrl});
}
window.close();
}
if (document.readyState !== 'loading') {
run();
} else {
document.addEventListener('DOMContentLoaded', function () {
run();
});
}
</script>
</body>
</html>

View file

@ -0,0 +1,20 @@
window.onload = function() {
//<editor-fold desc="Changeable Configuration Block">
// the following lines will be replaced by docker/configurator, when it runs in a docker-container
window.ui = SwaggerUIBundle({
url: "https://petstore.swagger.io/v2/swagger.json",
dom_id: '#swagger-ui',
deepLinking: true,
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
layout: "StandaloneLayout"
});
//</editor-fold>
};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long