This commit is contained in:
2026-02-07 21:41:43 +01:00
parent f17ea7ddc7
commit ae63817172
2 changed files with 42 additions and 19 deletions

View File

@@ -1,5 +1,6 @@
"""Customer CRUD API endpoints with automatic deployment on create.""" """Customer CRUD API endpoints with automatic deployment on create."""
import asyncio
import logging import logging
from datetime import datetime from datetime import datetime
from typing import Optional from typing import Optional
@@ -7,7 +8,7 @@ from typing import Optional
from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, Query, status from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, Query, status
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from app.database import get_db from app.database import SessionLocal, get_db
from app.dependencies import get_current_user from app.dependencies import get_current_user
from app.models import Customer, Deployment, DeploymentLog, User from app.models import Customer, Deployment, DeploymentLog, User
from app.services import netbird_service from app.services import netbird_service
@@ -59,11 +60,22 @@ async def create_customer(
logger.info("Customer %d (%s) created by %s.", customer.id, customer.subdomain, current_user.username) logger.info("Customer %d (%s) created by %s.", customer.id, customer.subdomain, current_user.username)
# Deploy in background # Deploy in background so the HTTP response returns immediately.
result = await netbird_service.deploy_customer(db, customer.id) # We create a dedicated DB session for the background task because
# the request session will be closed once the response is sent.
async def _deploy_in_background(customer_id: int) -> None:
bg_db = SessionLocal()
try:
await netbird_service.deploy_customer(bg_db, customer_id)
except Exception:
logger.exception("Background deployment failed for customer %d", customer_id)
finally:
bg_db.close()
background_tasks.add_task(_deploy_in_background, customer.id)
response = customer.to_dict() response = customer.to_dict()
response["deployment"] = result response["deployment"] = {"deployment_status": "deploying"}
return response return response
@@ -221,7 +233,10 @@ async def delete_customer(
) )
# Undeploy first (containers, NPM, files) # Undeploy first (containers, NPM, files)
await netbird_service.undeploy_customer(db, customer_id) try:
await netbird_service.undeploy_customer(db, customer_id)
except Exception:
logger.exception("Undeploy error for customer %d (continuing with delete)", customer_id)
# Delete customer record (cascades to deployment + logs) # Delete customer record (cascades to deployment + logs)
db.delete(customer) db.delete(customer)

View File

@@ -2,10 +2,10 @@
import logging import logging
from fastapi import APIRouter, Depends, HTTPException, status from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, status
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from app.database import get_db from app.database import SessionLocal, get_db
from app.dependencies import get_current_user from app.dependencies import get_current_user
from app.models import Customer, Deployment, User from app.models import Customer, Deployment, User
from app.services import docker_service, netbird_service from app.services import docker_service, netbird_service
@@ -17,35 +17,43 @@ router = APIRouter()
@router.post("/{customer_id}/deploy") @router.post("/{customer_id}/deploy")
async def manual_deploy( async def manual_deploy(
customer_id: int, customer_id: int,
background_tasks: BackgroundTasks,
current_user: User = Depends(get_current_user), current_user: User = Depends(get_current_user),
db: Session = Depends(get_db), db: Session = Depends(get_db),
): ):
"""Manually trigger deployment for a customer. """Manually trigger deployment for a customer.
Use this to re-deploy a customer whose previous deployment failed. Use this to re-deploy a customer whose previous deployment failed.
Runs in background and returns immediately.
Args: Args:
customer_id: Customer ID. customer_id: Customer ID.
Returns: Returns:
Deployment result dict. Acknowledgement dict.
""" """
customer = db.query(Customer).filter(Customer.id == customer_id).first() customer = db.query(Customer).filter(Customer.id == customer_id).first()
if not customer: if not customer:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Customer not found.") raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Customer not found.")
# Remove existing deployment if present customer.status = "deploying"
existing = db.query(Deployment).filter(Deployment.customer_id == customer_id).first() db.commit()
if existing:
await netbird_service.undeploy_customer(db, customer_id)
result = await netbird_service.deploy_customer(db, customer_id) async def _deploy_bg(cid: int) -> None:
if not result.get("success"): bg_db = SessionLocal()
raise HTTPException( try:
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, # Remove existing deployment if present
detail=result.get("error", "Deployment failed."), existing = bg_db.query(Deployment).filter(Deployment.customer_id == cid).first()
) if existing:
return result await netbird_service.undeploy_customer(bg_db, cid)
await netbird_service.deploy_customer(bg_db, cid)
except Exception:
logger.exception("Background re-deploy failed for customer %d", cid)
finally:
bg_db.close()
background_tasks.add_task(_deploy_bg, customer_id)
return {"message": "Deployment started in background.", "status": "deploying"}
@router.post("/{customer_id}/start") @router.post("/{customer_id}/start")