package services import ( "encoding/json" "fmt" "time" "git.linuxforward.com/byop/byop-engine/dbstore" "git.linuxforward.com/byop/byop-engine/models" "github.com/google/uuid" ) // DeploymentService handles business logic for deployments type DeploymentService struct { store *dbstore.DeploymentStore appStore *dbstore.AppStore templateStore *dbstore.TemplateStore clientStore *dbstore.ClientStore } // NewDeploymentService creates a new DeploymentService func NewDeploymentService( store *dbstore.DeploymentStore, appStore *dbstore.AppStore, templateStore *dbstore.TemplateStore, clientStore *dbstore.ClientStore, ) *DeploymentService { return &DeploymentService{ store: store, appStore: appStore, templateStore: templateStore, clientStore: clientStore, } } // CreateDeployment creates a new deployment func (s *DeploymentService) CreateDeployment(deployment *models.Deployment) error { // Generate UUID if not provided if deployment.ID == "" { deployment.ID = uuid.New().String() } // Validate the deployment if err := s.validateDeployment(deployment); err != nil { return fmt.Errorf("invalid deployment: %w", err) } // Set appropriate status deployment.Status = string(models.PENDING_DEPLOYMENT) // Set timestamps now := time.Now() deployment.LastDeployedAt = now // Handle deployed apps setup if err := s.setupDeployedApps(deployment); err != nil { return fmt.Errorf("failed to setup deployed apps: %w", err) } // Persist the deployment if err := s.store.Create(deployment); err != nil { return fmt.Errorf("failed to create deployment: %w", err) } // Trigger deployment process (this would normally be asynchronous) // This is a placeholder for your actual deployment logic go s.processDeployment(deployment.ID) return nil } // GetDeployment retrieves a deployment by ID func (s *DeploymentService) GetDeployment(id string) (*models.Deployment, error) { deployment, err := s.store.GetByID(id) if err != nil { return nil, fmt.Errorf("failed to retrieve deployment: %w", err) } if deployment != nil { // Deserialize config fields if err := s.deserializeConfigFields(deployment); err != nil { return nil, fmt.Errorf("failed to deserialize config fields: %w", err) } } return deployment, nil } // UpdateDeployment updates an existing deployment func (s *DeploymentService) UpdateDeployment(deployment *models.Deployment) error { // Validate the deployment ID if deployment.ID == "" { return fmt.Errorf("deployment ID is required for update") } // Check if deployment exists existingDeployment, err := s.store.GetByID(deployment.ID) if err != nil { return fmt.Errorf("failed to check if deployment exists: %w", err) } if existingDeployment == nil { return fmt.Errorf("deployment with ID %s not found", deployment.ID) } // Prevent updates to deployed apps if deployment is not in the right state if existingDeployment.Status != string(models.PENDING_DEPLOYMENT) && existingDeployment.Status != string(models.FAILED_DEPLOYMENT) && len(deployment.DeployedApps) > 0 { return fmt.Errorf("cannot update deployed apps when deployment is in %s state", existingDeployment.Status) } // Validate the deployment if err := s.validateDeployment(deployment); err != nil { return fmt.Errorf("invalid deployment: %w", err) } // If status was updated to "deploying", update LastDeployedAt if existingDeployment.Status != string(models.DEPLOYING) && deployment.Status == string(models.DEPLOYING) { deployment.LastDeployedAt = time.Now() } // Handle deployed apps setup if err := s.setupDeployedApps(deployment); err != nil { return fmt.Errorf("failed to setup deployed apps: %w", err) } // Persist the deployment if err := s.store.Update(deployment); err != nil { return fmt.Errorf("failed to update deployment: %w", err) } return nil } // DeleteDeployment deletes a deployment by ID func (s *DeploymentService) DeleteDeployment(id string) error { // Check if deployment exists deployment, err := s.store.GetByID(id) if err != nil { return fmt.Errorf("failed to check if deployment exists: %w", err) } if deployment == nil { return fmt.Errorf("deployment with ID %s not found", id) } // Set status to deleting deployment.Status = string(models.DELETING) if err := s.store.Update(deployment); err != nil { return fmt.Errorf("failed to update deployment status: %w", err) } // Trigger cleanup process (this would normally be asynchronous) // This is a placeholder for your actual cleanup logic go s.processDeploymentCleanup(id) return nil } // ListDeployments retrieves all deployments with optional filtering func (s *DeploymentService) ListDeployments(filter map[string]interface{}) ([]*models.Deployment, error) { deployments, err := s.store.List(filter) if err != nil { return nil, fmt.Errorf("failed to list deployments: %w", err) } // Deserialize config fields for each deployment for _, deployment := range deployments { if err := s.deserializeConfigFields(deployment); err != nil { return nil, fmt.Errorf("failed to deserialize config fields: %w", err) } } return deployments, nil } // GetDeploymentsByClientID retrieves deployments for a specific client func (s *DeploymentService) GetDeploymentsByClientID(clientID string) ([]*models.Deployment, error) { // Check if client exists client, err := s.clientStore.GetByID(clientID) if err != nil { return nil, fmt.Errorf("failed to check if client exists: %w", err) } if client == nil { return nil, fmt.Errorf("client with ID %s not found", clientID) } deployments, err := s.store.GetByClientID(clientID) if err != nil { return nil, fmt.Errorf("failed to retrieve deployments for client %s: %w", clientID, err) } // Deserialize config fields for each deployment for _, deployment := range deployments { if err := s.deserializeConfigFields(deployment); err != nil { return nil, fmt.Errorf("failed to deserialize config fields: %w", err) } } return deployments, nil } // GetDeploymentsByUserID retrieves deployments created by a specific user func (s *DeploymentService) GetDeploymentsByUserID(userID string) ([]*models.Deployment, error) { deployments, err := s.store.GetByUserID(userID) if err != nil { return nil, fmt.Errorf("failed to retrieve deployments for user %s: %w", userID, err) } // Deserialize config fields for each deployment for _, deployment := range deployments { if err := s.deserializeConfigFields(deployment); err != nil { return nil, fmt.Errorf("failed to deserialize config fields: %w", err) } } return deployments, nil } // GetDeploymentsByTemplateID retrieves deployments based on a specific template func (s *DeploymentService) GetDeploymentsByTemplateID(templateID string) ([]*models.Deployment, error) { // Check if template exists template, err := s.templateStore.GetByID(templateID) if err != nil { return nil, fmt.Errorf("failed to check if template exists: %w", err) } if template == nil { return nil, fmt.Errorf("template with ID %s not found", templateID) } deployments, err := s.store.GetByTemplateID(templateID) if err != nil { return nil, fmt.Errorf("failed to retrieve deployments for template %s: %w", templateID, err) } // Deserialize config fields for each deployment for _, deployment := range deployments { if err := s.deserializeConfigFields(deployment); err != nil { return nil, fmt.Errorf("failed to deserialize config fields: %w", err) } } return deployments, nil } // UpdateDeploymentStatus updates the status of a deployment func (s *DeploymentService) UpdateDeploymentStatus(id string, status string) error { deployment, err := s.store.GetByID(id) if err != nil { return fmt.Errorf("failed to retrieve deployment: %w", err) } if deployment == nil { return fmt.Errorf("deployment with ID %s not found", id) } // Update the status deployment.Status = status // If status is being set to "deploying", update LastDeployedAt if status == string(models.DEPLOYING) { deployment.LastDeployedAt = time.Now() } if err := s.store.Update(deployment); err != nil { return fmt.Errorf("failed to update deployment status: %w", err) } return nil } // validateDeployment validates a deployment func (s *DeploymentService) validateDeployment(deployment *models.Deployment) error { // Validate required fields if deployment.Name == "" { return fmt.Errorf("deployment name is required") } // Validate relationships if deployment.ClientID == "" { return fmt.Errorf("client ID is required") } client, err := s.clientStore.GetByID(deployment.ClientID) if err != nil { return fmt.Errorf("failed to check client: %w", err) } if client == nil { return fmt.Errorf("client with ID %s not found", deployment.ClientID) } if deployment.TemplateID == "" { return fmt.Errorf("template ID is required") } template, err := s.templateStore.GetByID(deployment.TemplateID) if err != nil { return fmt.Errorf("failed to check template: %w", err) } if template == nil { return fmt.Errorf("template with ID %s not found", deployment.TemplateID) } return nil } // setupDeployedApps sets up deployed apps based on the template func (s *DeploymentService) setupDeployedApps(deployment *models.Deployment) error { // If deployment already has deployed apps defined, we assume they're set up correctly if len(deployment.DeployedApps) > 0 { return nil } // Get the template template, err := s.templateStore.GetByID(deployment.TemplateID) if err != nil { return fmt.Errorf("failed to retrieve template: %w", err) } if template == nil { return fmt.Errorf("template with ID %s not found", deployment.TemplateID) } // Use the template config to set up deployed apps var templateConfig models.TemplateConfig if err := json.Unmarshal([]byte(template.ConfigJSON), &templateConfig); err != nil { return fmt.Errorf("failed to parse template config: %w", err) } // Create deployed apps for each app in the template for _, appConfig := range templateConfig.Apps { // Get the app app, err := s.appStore.GetByID(appConfig.ID) if err != nil { return fmt.Errorf("failed to retrieve app: %w", err) } if app == nil { return fmt.Errorf("app with ID %s not found", appConfig.ID) } // Create a deployed app deployedApp := models.DeployedApp{ ID: uuid.New().String(), DeploymentID: deployment.ID, AppID: app.ID, Status: string(models.PENDING_APP), Version: app.Version, URL: "", // Will be set during deployment PodCount: appConfig.Autoscaling.MinReplicas, HealthStatus: string(models.HEALTHY), Resources: models.ResourceAllocation{ CPU: appConfig.Resources.CPU, Memory: appConfig.Resources.Memory, Storage: appConfig.Resources.Storage, }, } // Add to deployment deployment.DeployedApps = append(deployment.DeployedApps, deployedApp) } return nil } // processDeployment handles the actual deployment process func (s *DeploymentService) processDeployment(deploymentID string) { // This would be an async process in a real system // For now, we just update the status after a short delay to simulate the process // Update status to deploying _ = s.UpdateDeploymentStatus(deploymentID, string(models.DEPLOYING)) // In a real system, this would be where you'd: // 1. Provision infrastructure // 2. Deploy containers/apps // 3. Configure networking // 4. Setup monitoring // etc. // For this demo, we'll just update the status after a short delay time.Sleep(2 * time.Second) // Update status to deployed or failed (randomly for demonstration) if time.Now().Unix()%2 == 0 { // Random success/failure _ = s.UpdateDeploymentStatus(deploymentID, string(models.DEPLOYED)) } else { _ = s.UpdateDeploymentStatus(deploymentID, string(models.FAILED_DEPLOYMENT)) } } // processDeploymentCleanup handles the cleanup process for deleted deployments func (s *DeploymentService) processDeploymentCleanup(deploymentID string) { // This would be an async process in a real system // In a real system, this would: // 1. Deprovision infrastructure // 2. Clean up resources // 3. Remove configuration // For this demo, we'll just delete after a short delay time.Sleep(2 * time.Second) // Delete the deployment from the database _ = s.store.Delete(deploymentID) } // deserializeConfigFields deserializes JSON config fields from strings func (s *DeploymentService) deserializeConfigFields(deployment *models.Deployment) error { // Deserialize logs config if deployment.LogsConfig != "" { var logsConfig models.LogConfiguration if err := json.Unmarshal([]byte(deployment.LogsConfig), &logsConfig); err != nil { return fmt.Errorf("failed to unmarshal logs config: %w", err) } // We could set this on the deployment if needed } // Deserialize metrics config if deployment.MetricsConfig != "" { var metricsConfig models.MetricsConfiguration if err := json.Unmarshal([]byte(deployment.MetricsConfig), &metricsConfig); err != nil { return fmt.Errorf("failed to unmarshal metrics config: %w", err) } // We could set this on the deployment if needed } // Deserialize alerts config if deployment.AlertsConfig != "" { var alertsConfig []models.AlertConfiguration if err := json.Unmarshal([]byte(deployment.AlertsConfig), &alertsConfig); err != nil { return fmt.Errorf("failed to unmarshal alerts config: %w", err) } // We could set this on the deployment if needed } return nil }