Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
23 changes: 12 additions & 11 deletions platform-api/src/internal/constants/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,17 +65,18 @@ var (
)

var (
ErrDeploymentNotFound = errors.New("deployment not found")
ErrDeploymentNotActive = errors.New("no active deployment found for this API on the gateway")
ErrDeploymentIsDeployed = errors.New("cannot delete an active deployment - undeploy it first")
ErrDeploymentAlreadyActive = errors.New("deployment is already active")
ErrBaseDeploymentNotFound = errors.New("base deployment not found")
ErrInvalidDeploymentStatus = errors.New("invalid deployment status")
ErrDeploymentNameRequired = errors.New("deployment name is required")
ErrDeploymentBaseRequired = errors.New("base is required")
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")
ErrDeploymentNotFound = errors.New("deployment not found")
ErrDeploymentNotActive = errors.New("no active deployment found for this API on the gateway")
ErrDeploymentIsDeployed = errors.New("cannot delete an active deployment - undeploy it first")
ErrDeploymentAlreadyActive = errors.New("deployment is already active")
ErrBaseDeploymentNotFound = errors.New("base deployment not found")
ErrInvalidDeploymentStatus = errors.New("invalid deployment status")
ErrDeploymentNameRequired = errors.New("deployment name is required")
ErrDeploymentBaseRequired = errors.New("base is required")
ErrDeploymentGatewayIDRequired = errors.New("gatewayId is required")
ErrAPINoBackendServices = errors.New("API must have at least one backend service attached before deployment")
ErrDeploymentAlreadyDeployed = errors.New("cannot restore to the 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