From 308f82740d5b234132241a179d709dd2082b0127 Mon Sep 17 00:00:00 2001 From: dongli Date: Sat, 7 Jun 2025 11:32:48 -0700 Subject: [PATCH] More impl for devops --- .../devops/app/common/config/site_settings.py | 5 + .../app/common/daos/code_depot/__init__.py | 6 ++ .../common/daos/code_depot/code_depot_dao.py | 19 ++++ .../app/common/daos/deployment/__init__.py | 2 +- .../common/daos/deployment/deployment_dao.py | 5 +- apps/devops/app/common/models/__init__.py | 6 +- apps/devops/app/routes/deployment/apis.py | 14 +++ apps/devops/app/routes/deployment/service.py | 100 +++++++++++------- 8 files changed, 112 insertions(+), 45 deletions(-) create mode 100644 apps/devops/app/common/daos/code_depot/__init__.py create mode 100644 apps/devops/app/common/daos/code_depot/code_depot_dao.py diff --git a/apps/devops/app/common/config/site_settings.py b/apps/devops/app/common/config/site_settings.py index 76e5af1..9a96914 100644 --- a/apps/devops/app/common/config/site_settings.py +++ b/apps/devops/app/common/config/site_settings.py @@ -18,6 +18,11 @@ class SiteSettings(BaseSettings): BASE_PATH: str = os.path.dirname(os.path.dirname((os.path.abspath(__file__)))) + BASE_GITEA_URL: str = "https://gitea.freeleaps.mathmast.com" + + # TODO: confirm with Zhenyu + BASE_RECONSILE_URL: str = "https://reconcile.freeleaps.mathmast.com" + class Config: env_file = ".devbase-webapi.env" env_file_encoding = "utf-8" diff --git a/apps/devops/app/common/daos/code_depot/__init__.py b/apps/devops/app/common/daos/code_depot/__init__.py new file mode 100644 index 0000000..7a4a4dc --- /dev/null +++ b/apps/devops/app/common/daos/code_depot/__init__.py @@ -0,0 +1,6 @@ +from app.common.daos.code_depot.code_depot_dao import CodeDepotDao + +code_depot_dao = CodeDepotDao() + +def get_code_depot_dao() -> CodeDepotDao: + return code_depot_dao \ No newline at end of file diff --git a/apps/devops/app/common/daos/code_depot/code_depot_dao.py b/apps/devops/app/common/daos/code_depot/code_depot_dao.py new file mode 100644 index 0000000..46539ad --- /dev/null +++ b/apps/devops/app/common/daos/code_depot/code_depot_dao.py @@ -0,0 +1,19 @@ +from app.common.models.code_depot.code_depot import CodeDepotDoc + + +class CodeDepotDao(): + + def __init__(self): + pass + + async def get_code_depot_by_product_id(self, product_id: str) -> CodeDepotDoc: + """ + Retrieve code depot by product id + """ + return await CodeDepotDoc.find_one({"product_id": product_id}) + + async def insert_code_depot(self, code_depot: CodeDepotDoc) -> CodeDepotDoc: + """ + Insert a new code depot into the database + """ + return await CodeDepotDoc.insert_one(code_depot) \ No newline at end of file diff --git a/apps/devops/app/common/daos/deployment/__init__.py b/apps/devops/app/common/daos/deployment/__init__.py index 4ee4c85..c97fd9d 100644 --- a/apps/devops/app/common/daos/deployment/__init__.py +++ b/apps/devops/app/common/daos/deployment/__init__.py @@ -2,5 +2,5 @@ from app.common.daos.deployment.deployment_dao import DeploymentDao deployment_dao = DeploymentDao() -def get_hello_world_dao() -> DeploymentDao: +def get_deployment_dao() -> DeploymentDao: return deployment_dao \ No newline at end of file diff --git a/apps/devops/app/common/daos/deployment/deployment_dao.py b/apps/devops/app/common/daos/deployment/deployment_dao.py index f34565a..ef8b18c 100644 --- a/apps/devops/app/common/daos/deployment/deployment_dao.py +++ b/apps/devops/app/common/daos/deployment/deployment_dao.py @@ -5,9 +5,10 @@ class DeploymentDao(): def __init__(self): pass - async def create_deployment(self, deployment_data: Deployment): + async def create_deployment(self, deployment_data: Deployment) -> Deployment: # Logic to create a new deployment - Deployment.insert(deployment_data) + return await Deployment.insert(deployment_data) + async def get_deployments_by_deployment_id(self, deployment_id: str): # Logic to get a deployment by ID diff --git a/apps/devops/app/common/models/__init__.py b/apps/devops/app/common/models/__init__.py index dd9cfe5..20aacb2 100644 --- a/apps/devops/app/common/models/__init__.py +++ b/apps/devops/app/common/models/__init__.py @@ -1,4 +1,6 @@ +from app.common.models.code_depot.code_depot import CodeDepotDoc from app.common.models.deployment.deployment import Deployment -# list of beanie document models -db_models = [Deployment] \ No newline at end of file +# list of beanie document models, +# must add here so that the mongo db collection can be automatically created +db_models = [Deployment, CodeDepotDoc] \ No newline at end of file diff --git a/apps/devops/app/routes/deployment/apis.py b/apps/devops/app/routes/deployment/apis.py index c6ebefc..a247742 100644 --- a/apps/devops/app/routes/deployment/apis.py +++ b/apps/devops/app/routes/deployment/apis.py @@ -4,6 +4,7 @@ from typing import List from fastapi import APIRouter, Depends from loguru import logger +from app.common.models import CodeDepotDoc from app.common.models.deployment.deployment import Deployment, InitDeploymentRequest from app.routes.deployment.service import DeploymentService, get_deployment_service @@ -31,3 +32,16 @@ async def check_deployment_status( ) -> List[Deployment]: return await service.check_deployment_status(deployment_id) +@router.post("/createDummyCodeDepot") +async def create_dummy_code_depot( + service: DeploymentService = Depends(get_deployment_service) +) -> CodeDepotDoc: + """ + Create a dummy code depot for testing purposes. + """ + try: + depot_name = await service.create_dummy_code_depot() + return depot_name + except Exception as e: + logger.error(f"Failed to create dummy code depot: {e}") + raise e diff --git a/apps/devops/app/routes/deployment/service.py b/apps/devops/app/routes/deployment/service.py index 9093d77..b0f27bf 100644 --- a/apps/devops/app/routes/deployment/service.py +++ b/apps/devops/app/routes/deployment/service.py @@ -3,9 +3,14 @@ from collections import defaultdict from datetime import datetime, timedelta from typing import List -from fastapi import HTTPException +import httpx +from fastapi import HTTPException, Depends +from app.common.config.site_settings import site_settings +from app.common.daos.code_depot import get_code_depot_dao, CodeDepotDao +from app.common.daos.deployment import DeploymentDao, get_deployment_dao from app.common.models import Deployment +from app.common.models.code_depot.code_depot import CodeDepotDoc, DepotStatus from app.common.models.deployment.deployment import InitDeploymentRequest @@ -16,27 +21,25 @@ class DeploymentService: async def init_deployment( self, - request: InitDeploymentRequest + request: InitDeploymentRequest, + dao: DeploymentDao = Depends(get_deployment_dao) ) -> Deployment: """ """ # TODO validate permission with user_id # currently skip - git_url = await self._retrieve_git_url_by_product_id(request.product_id) + code_depot = await self._get_code_depot_by_product_id(request.product_id) + + git_url = await self._compose_git_url(code_depot.depot_name) - product_initialized = await self._check_if_project_initialized(git_url, request.product_id) - if not product_initialized: - await self._init_product(git_url, request.product_id) # retrieve project name project_name = "TODO" - # retrieve product info + # retrieve product info, depot name should be the same as product name product_id = request.product_id - product_name = "TODO" - - + product_name = code_depot.depot_name deployment = Deployment.model_construct( deployment_id = str(uuid.uuid4()), @@ -57,8 +60,8 @@ class DeploymentService: ) await self._start_deployment(deployment) + res = await dao.create_deployment(deployment) - res = await deployment.insert() return res async def check_deployment_status( @@ -111,47 +114,64 @@ class DeploymentService: return True - async def _retrieve_git_url_by_product_id( + async def _get_code_depot_by_product_id( self, - product_id: str + product_id: str, + code_depot_dao: CodeDepotDao = Depends(get_code_depot_dao) + ) -> CodeDepotDoc: + """ + Retrieve code depot by product id + """ + code_depot = await code_depot_dao.get_code_depot_by_product_id(product_id) + if not code_depot: + raise HTTPException(status_code=404, + detail="Code depot not found for the given product id, " + "please initialize the product first" + ) + return code_depot + + async def _compose_git_url( + self, + code_depot_name: str, + gitea_base_url: str = site_settings.BASE_GITEA_URL ) -> str: """ Retrieve git url by product id """ - # TODO implement this function - return "TODO-git_url" - - async def _check_if_project_initialized( - self, - git_url: str, - product_id: str - ) -> bool: - """ - Check if the project has been initialized - """ - # TODO implement this function - return True - - async def _init_product( - self, - git_url: str, - product_id: str - ) -> bool: - """ - Initialize the product - """ - # TODO implement this function - pass + return f"{gitea_base_url}/prodcuts/{code_depot_name.lower()}.git" async def _start_deployment( self, - deployment: Deployment + deployment: Deployment, + reconsile_base_url: str = site_settings.BASE_RECONSILE_URL, ) -> bool: """ Start the deployment """ - # TODO implement this function - pass + async with httpx.AsyncClient() as client: + response = await client.post( + f"{reconsile_base_url}/api/devops/reconcile", + json=deployment.model_dump() + ) + if response.status_code != 200: + raise HTTPException(status_code=response.status_code, detail=response.text) + return True + + async def create_dummy_code_depot( + self, + code_depot_dao: CodeDepotDao = Depends(get_code_depot_dao) + ) -> CodeDepotDoc: + """ + Create a dummy code depot for testing purposes. + """ + depot_name = f"dummy-depot-{uuid.uuid4()}" + code_depot = CodeDepotDoc( + depot_name=depot_name, + product_id="dummy-product-id", + depot_status=DepotStatus.CREATED + ) + + return await code_depot.insert_one(code_depot) deployment_service = DeploymentService()