Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions platform-api/src/internal/constants/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ var (
ErrDeploymentGatewayIDRequired = errors.New("gatewayId is required")
ErrAPINoBackendServices = errors.New("API must have at least one backend service attached before deployment")
ErrDeploymentAlreadyDeployed = errors.New("cannot rollback to currently deployed deployment")
ErrGatewayIDMismatch = errors.New("gateway ID mismatch: deployment is bound to a different gateway")
)

var (
Expand Down
52 changes: 37 additions & 15 deletions platform-api/src/internal/handler/deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ func (h *DeploymentHandler) DeployAPI(c *gin.Context) {
c.JSON(http.StatusCreated, deployment)
}

// UndeployDeployment handles POST /api/v1/apis/:apiId/deployments/:deploymentId/undeploy
// UndeployDeployment handles POST /api/v1/apis/:apiId/deployments/undeploy
// Undeploys an active deployment by changing its status to UNDEPLOYED
func (h *DeploymentHandler) UndeployDeployment(c *gin.Context) {
orgId, exists := middleware.GetOrganizationFromContext(c)
Expand All @@ -138,7 +138,8 @@ func (h *DeploymentHandler) UndeployDeployment(c *gin.Context) {
}

apiId := c.Param("apiId")
deploymentId := c.Param("deploymentId")
deploymentId := c.Query("deploymentId")
gatewayId := c.Query("gatewayId")

if apiId == "" {
c.JSON(http.StatusBadRequest, utils.NewErrorResponse(400, "Bad Request",
Expand All @@ -147,11 +148,16 @@ func (h *DeploymentHandler) UndeployDeployment(c *gin.Context) {
}
if deploymentId == "" {
c.JSON(http.StatusBadRequest, utils.NewErrorResponse(400, "Bad Request",
"Deployment ID is required"))
"deploymentId query parameter is required"))
return
}
if gatewayId == "" {
c.JSON(http.StatusBadRequest, utils.NewErrorResponse(400, "Bad Request",
"gatewayId query parameter is required"))
return
}

deployment, err := h.deploymentService.UndeployDeploymentByHandle(apiId, deploymentId, orgId)
deployment, err := h.deploymentService.UndeployDeploymentByHandle(apiId, deploymentId, gatewayId, orgId)
if err != nil {
if errors.Is(err, constants.ErrAPINotFound) {
c.JSON(http.StatusNotFound, utils.NewErrorResponse(404, "Not Found",
Expand All @@ -173,17 +179,22 @@ func (h *DeploymentHandler) UndeployDeployment(c *gin.Context) {
"No active deployment found for this API on the gateway"))
return
}
log.Printf("[ERROR] Failed to undeploy: apiId=%s deploymentId=%s error=%v", apiId, deploymentId, err)
if errors.Is(err, constants.ErrGatewayIDMismatch) {
c.JSON(http.StatusBadRequest, utils.NewErrorResponse(400, "Bad Request",
"Deployment is bound to a different gateway"))
return
}
log.Printf("[ERROR] Failed to undeploy: apiId=%s deploymentId=%s gatewayId=%s error=%v", apiId, deploymentId, gatewayId, err)
c.JSON(http.StatusInternalServerError, utils.NewErrorResponse(500, "Internal Server Error", "Failed to undeploy deployment"))
return
}

c.JSON(http.StatusOK, deployment)
}

// RollbackDeployment handles POST /api/v1/apis/:apiId/rollback-deployment
// Rolls back to a previous deployment (ARCHIVED or UNDEPLOYED)
func (h *DeploymentHandler) RollbackDeployment(c *gin.Context) {
// RestoreDeployment handles POST /api/v1/apis/:apiId/deployments/restore
// Restores a previous deployment (ARCHIVED or UNDEPLOYED)
func (h *DeploymentHandler) RestoreDeployment(c *gin.Context) {
orgId, exists := middleware.GetOrganizationFromContext(c)
if !exists {
c.JSON(http.StatusUnauthorized, utils.NewErrorResponse(401, "Unauthorized",
Expand All @@ -193,6 +204,7 @@ func (h *DeploymentHandler) RollbackDeployment(c *gin.Context) {

apiId := c.Param("apiId")
deploymentId := c.Query("deploymentId")
gatewayId := c.Query("gatewayId")

if apiId == "" {
c.JSON(http.StatusBadRequest, utils.NewErrorResponse(400, "Bad Request",
Expand All @@ -204,8 +216,13 @@ func (h *DeploymentHandler) RollbackDeployment(c *gin.Context) {
"deploymentId query parameter is required"))
return
}
if gatewayId == "" {
c.JSON(http.StatusBadRequest, utils.NewErrorResponse(400, "Bad Request",
"gatewayId query parameter is required"))
return
}

deployment, err := h.deploymentService.RollbackDeploymentByHandle(apiId, deploymentId, orgId)
deployment, err := h.deploymentService.RestoreDeploymentByHandle(apiId, deploymentId, gatewayId, orgId)
if err != nil {
if errors.Is(err, constants.ErrAPINotFound) {
c.JSON(http.StatusNotFound, utils.NewErrorResponse(404, "Not Found",
Expand All @@ -224,11 +241,16 @@ func (h *DeploymentHandler) RollbackDeployment(c *gin.Context) {
}
if errors.Is(err, constants.ErrDeploymentAlreadyDeployed) {
c.JSON(http.StatusConflict, utils.NewErrorResponse(409, "Conflict",
"Cannot rollback to currently deployed deployment"))
"Cannot restore currently deployed deployment"))
return
}
if errors.Is(err, constants.ErrGatewayIDMismatch) {
c.JSON(http.StatusBadRequest, utils.NewErrorResponse(400, "Bad Request",
"Deployment is bound to a different gateway"))
return
}
log.Printf("[ERROR] Failed to rollback deployment: apiId=%s deploymentId=%s error=%v", apiId, deploymentId, err)
c.JSON(http.StatusInternalServerError, utils.NewErrorResponse(500, "Internal Server Error", "Failed to rollback deployment"))
log.Printf("[ERROR] Failed to restore deployment: apiId=%s deploymentId=%s gatewayId=%s error=%v", apiId, deploymentId, gatewayId, err)
c.JSON(http.StatusInternalServerError, utils.NewErrorResponse(500, "Internal Server Error", "Failed to restore deployment"))
return
}

Expand Down Expand Up @@ -375,11 +397,11 @@ func (h *DeploymentHandler) GetDeployments(c *gin.Context) {
func (h *DeploymentHandler) RegisterRoutes(r *gin.Engine) {
apiGroup := r.Group("/api/v1/apis/:apiId")
{
apiGroup.POST("/deploy", h.DeployAPI)
apiGroup.POST("/rollback-deployment", h.RollbackDeployment)
apiGroup.POST("/deployments", h.DeployAPI)
apiGroup.POST("/deployments/undeploy", h.UndeployDeployment)
apiGroup.POST("/deployments/restore", h.RestoreDeployment)
apiGroup.GET("/deployments", h.GetDeployments)
apiGroup.GET("/deployments/:deploymentId", h.GetDeployment)
apiGroup.POST("/deployments/:deploymentId/undeploy", h.UndeployDeployment)
apiGroup.DELETE("/deployments/:deploymentId", h.DeleteDeployment)
}
}
26 changes: 18 additions & 8 deletions platform-api/src/internal/service/deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,8 +223,8 @@ func (s *DeploymentService) DeployAPI(apiUUID string, req *dto.DeployAPIRequest,
}, nil
}

// RollbackDeployment rolls back to a previous deployment (can be ARCHIVED or UNDEPLOYED)
func (s *DeploymentService) RollbackDeployment(apiUUID, deploymentID, orgUUID string) (*dto.DeploymentResponse, error) {
// RestoreDeployment restores a previous deployment (can be ARCHIVED or UNDEPLOYED)
func (s *DeploymentService) RestoreDeployment(apiUUID, deploymentID, gatewayID, orgUUID string) (*dto.DeploymentResponse, error) {
// Verify target deployment exists and belongs to the API
targetDeployment, err := s.apiRepo.GetDeploymentWithContent(deploymentID, apiUUID, orgUUID)
if err != nil {
Expand All @@ -234,6 +234,11 @@ func (s *DeploymentService) RollbackDeployment(apiUUID, deploymentID, orgUUID st
return nil, constants.ErrDeploymentNotFound
}

// Validate that the provided gatewayID matches the deployment's bound gateway
if targetDeployment.GatewayID != gatewayID {
return nil, constants.ErrGatewayIDMismatch
}

// Verify target deployment is NOT currently DEPLOYED
currentDeploymentID, status, _, err := s.apiRepo.GetDeploymentStatus(apiUUID, orgUUID, targetDeployment.GatewayID)
if err != nil {
Expand Down Expand Up @@ -286,7 +291,7 @@ func (s *DeploymentService) RollbackDeployment(apiUUID, deploymentID, orgUUID st
}

// UndeployDeployment undeploys an active deployment
func (s *DeploymentService) UndeployDeployment(apiUUID, deploymentID, orgUUID string) (*dto.DeploymentResponse, error) {
func (s *DeploymentService) UndeployDeployment(apiUUID, deploymentID, gatewayID, orgUUID string) (*dto.DeploymentResponse, error) {
// Verify deployment exists and belongs to API
deployment, err := s.apiRepo.GetDeploymentWithState(deploymentID, apiUUID, orgUUID)
if err != nil {
Expand All @@ -296,6 +301,11 @@ func (s *DeploymentService) UndeployDeployment(apiUUID, deploymentID, orgUUID st
return nil, constants.ErrDeploymentNotFound
}

// Validate that the provided gatewayID matches the deployment's bound gateway
if deployment.GatewayID != gatewayID {
return nil, constants.ErrGatewayIDMismatch
}

// Verify deployment is currently DEPLOYED (status already populated by GetDeploymentWithState)
if deployment.Status == nil || *deployment.Status != model.DeploymentStatusDeployed {
return nil, constants.ErrDeploymentNotActive
Expand Down Expand Up @@ -562,15 +572,15 @@ func (s *DeploymentService) DeployAPIByHandle(apiHandle string, req *dto.DeployA
return s.DeployAPI(apiUUID, req, orgUUID)
}

// RollbackDeploymentByHandle rolls back to a previous deployment using API handle
func (s *DeploymentService) RollbackDeploymentByHandle(apiHandle, deploymentID, orgUUID string) (*dto.DeploymentResponse, error) {
// RestoreDeploymentByHandle restores a previous deployment using API handle
func (s *DeploymentService) RestoreDeploymentByHandle(apiHandle, deploymentID, gatewayID, orgUUID string) (*dto.DeploymentResponse, error) {
// Convert API handle to UUID
apiUUID, err := s.getAPIUUIDByHandle(apiHandle, orgUUID)
if err != nil {
return nil, err
}

return s.RollbackDeployment(apiUUID, deploymentID, orgUUID)
return s.RestoreDeployment(apiUUID, deploymentID, gatewayID, orgUUID)
}

// getAPIUUIDByHandle retrieves the internal UUID for an API by its handle
Expand Down Expand Up @@ -623,14 +633,14 @@ func (s *DeploymentService) GetDeploymentsByHandle(apiHandle, gatewayID, status,
}

// UndeployDeploymentByHandle undeploys a deployment using API handle
func (s *DeploymentService) UndeployDeploymentByHandle(apiHandle, deploymentID, orgUUID string) (*dto.DeploymentResponse, error) {
func (s *DeploymentService) UndeployDeploymentByHandle(apiHandle, deploymentID, gatewayID, orgUUID string) (*dto.DeploymentResponse, error) {
// Convert API handle to UUID
apiUUID, err := s.getAPIUUIDByHandle(apiHandle, orgUUID)
if err != nil {
return nil, err
}

return s.UndeployDeployment(apiUUID, deploymentID, orgUUID)
return s.UndeployDeployment(apiUUID, deploymentID, gatewayID, orgUUID)
}

// DeleteDeploymentByHandle deletes a deployment using API handle
Expand Down
Loading
Loading