123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432 |
- 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
- }
|