123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360 |
- package dbstore
- import (
- "encoding/json"
- "fmt"
- "git.linuxforward.com/byop/byop-engine/dbmanager"
- "git.linuxforward.com/byop/byop-engine/models"
- "github.com/google/uuid"
- "gorm.io/gorm"
- )
- // DeploymentStore handles database operations for deployments
- type DeploymentStore struct {
- db *gorm.DB
- }
- // NewDeploymentStore creates a new DeploymentStore
- func NewDeploymentStore(dbManager dbmanager.DbManager) *DeploymentStore {
- return &DeploymentStore{
- db: dbManager.GetDB(),
- }
- }
- // Create creates a new deployment
- func (ds *DeploymentStore) CreateDeployment(deployment *models.Deployment) error {
- // Generate ID if not provided
- if deployment.ID == "" {
- deployment.ID = uuid.New().String()
- }
- // Ensure logs, metrics, and alerts config are properly JSON serialized
- if err := ds.serializeConfigFields(deployment); err != nil {
- return fmt.Errorf("failed to serialize config fields: %w", err)
- }
- // Create deployment in a transaction to handle deployed apps
- return ds.db.Transaction(func(tx *gorm.DB) error {
- // Create the deployment
- if err := tx.Create(deployment).Error; err != nil {
- return err
- }
- // Create any deployed apps in the same transaction
- for i := range deployment.DeployedComponents {
- app := &deployment.DeployedComponents[i]
- // Ensure each deployed app has an ID
- if app.ID == "" {
- app.ID = uuid.New().String()
- }
- // Set the deployment ID (ensure relationship is maintained)
- app.DeploymentID = deployment.ID
- // Create the deployed app
- if err := tx.Create(app).Error; err != nil {
- return err
- }
- // Handle resources if provided
- if app.Resources != (models.ResourceAllocation{}) {
- resource := models.DeployedComponentResource{
- ID: uuid.New().String(),
- DeployedComponentID: app.ID,
- CPU: app.Resources.CPU,
- CPUUsage: app.Resources.CPUUsage,
- Memory: app.Resources.Memory,
- MemoryUsage: app.Resources.MemoryUsage,
- Storage: app.Resources.Storage,
- StorageUsage: app.Resources.StorageUsage,
- }
- if err := tx.Create(&resource).Error; err != nil {
- return err
- }
- }
- }
- return nil
- })
- }
- // GetByID retrieves a deployment by ID
- func (ds *DeploymentStore) GetByID(id string) (*models.Deployment, error) {
- var deployment models.Deployment
- // Get deployment with all related deployed apps
- err := ds.db.
- Preload("DeployedComponents").
- First(&deployment, "id = ?", id).Error
- if err != nil {
- if err == gorm.ErrRecordNotFound {
- return nil, nil // No deployment found
- }
- return nil, fmt.Errorf("failed to get deployment: %w", err)
- }
- // Load resources for each deployed app
- for i, app := range deployment.DeployedComponents {
- var resource models.DeployedComponentResource
- if err := ds.db.Where("deployed_app_id = ?", app.ID).First(&resource).Error; err != nil {
- if err != gorm.ErrRecordNotFound {
- return nil, fmt.Errorf("failed to get resources for deployed app: %w", err)
- }
- } else {
- deployment.DeployedComponents[i].Resources = models.ResourceAllocation{
- CPU: resource.CPU,
- CPUUsage: resource.CPUUsage,
- Memory: resource.Memory,
- MemoryUsage: resource.MemoryUsage,
- Storage: resource.Storage,
- StorageUsage: resource.StorageUsage,
- }
- }
- }
- // Deserialize config fields
- if err := ds.deserializeConfigFields(&deployment); err != nil {
- return nil, fmt.Errorf("failed to deserialize config fields: %w", err)
- }
- return &deployment, nil
- }
- // Update updates an existing deployment
- func (ds *DeploymentStore) Update(deployment *models.Deployment) error {
- // Ensure logs, metrics, and alerts config are properly JSON serialized
- if err := ds.serializeConfigFields(deployment); err != nil {
- return fmt.Errorf("failed to serialize config fields: %w", err)
- }
- // Use transaction to handle deployment and deployed apps
- return ds.db.Transaction(func(tx *gorm.DB) error {
- // Update the deployment
- if err := tx.Save(deployment).Error; err != nil {
- return err
- }
- // Handle deployed apps - this is trickier as we need to compare with existing apps
- var existingComponents []models.DeployedComponent
- if err := tx.Where("deployment_id = ?", deployment.ID).Find(&existingComponents).Error; err != nil {
- return err
- }
- // Create a map of existing app IDs for quick lookup
- existingComponentMap := make(map[string]bool)
- for _, app := range existingComponents {
- existingComponentMap[app.ID] = true
- }
- // Process each app in the updated deployment
- for i := range deployment.DeployedComponents {
- app := &deployment.DeployedComponents[i]
- // If app has ID and exists, update it
- if app.ID != "" && existingComponentMap[app.ID] {
- if err := tx.Save(app).Error; err != nil {
- return err
- }
- delete(existingComponentMap, app.ID)
- } else {
- // New app, create it
- if app.ID == "" {
- app.ID = uuid.New().String()
- }
- app.DeploymentID = deployment.ID
- if err := tx.Create(app).Error; err != nil {
- return err
- }
- }
- // Handle resources
- if app.Resources != (models.ResourceAllocation{}) {
- var resource models.DeployedComponentResource
- result := tx.Where("deployed_app_id = ?", app.ID).First(&resource)
- if result.Error != nil && result.Error != gorm.ErrRecordNotFound {
- return result.Error
- }
- if result.Error == gorm.ErrRecordNotFound {
- // Create new resource
- resource = models.DeployedComponentResource{
- ID: uuid.New().String(),
- DeployedComponentID: app.ID,
- CPU: app.Resources.CPU,
- CPUUsage: app.Resources.CPUUsage,
- Memory: app.Resources.Memory,
- MemoryUsage: app.Resources.MemoryUsage,
- Storage: app.Resources.Storage,
- StorageUsage: app.Resources.StorageUsage,
- }
- if err := tx.Create(&resource).Error; err != nil {
- return err
- }
- } else {
- // Update existing resource
- resource.CPU = app.Resources.CPU
- resource.CPUUsage = app.Resources.CPUUsage
- resource.Memory = app.Resources.Memory
- resource.MemoryUsage = app.Resources.MemoryUsage
- resource.Storage = app.Resources.Storage
- resource.StorageUsage = app.Resources.StorageUsage
- if err := tx.Save(&resource).Error; err != nil {
- return err
- }
- }
- }
- }
- // Delete any apps that are no longer part of the deployment
- for appID := range existingComponentMap {
- if err := tx.Delete(&models.DeployedComponent{}, "id = ?", appID).Error; err != nil {
- return err
- }
- // Delete associated resources
- if err := tx.Delete(&models.DeployedComponentResource{}, "deployed_app_id = ?", appID).Error; err != nil && err != gorm.ErrRecordNotFound {
- return err
- }
- }
- return nil
- })
- }
- // Delete deletes a deployment by ID
- func (ds *DeploymentStore) Delete(id string) error {
- return ds.db.Transaction(func(tx *gorm.DB) error {
- // Delete associated DeployedComponentResources
- var deployedComponents []models.DeployedComponent
- if err := tx.Where("deployment_id = ?", id).Find(&deployedComponents).Error; err != nil {
- return err
- }
- for _, app := range deployedComponents {
- if err := tx.Delete(&models.DeployedComponentResource{}, "deployed_app_id = ?", app.ID).Error; err != nil && err != gorm.ErrRecordNotFound {
- return err
- }
- }
- // Delete deployed apps
- if err := tx.Delete(&models.DeployedComponent{}, "deployment_id = ?", id).Error; err != nil && err != gorm.ErrRecordNotFound {
- return err
- }
- // Delete the deployment itself
- return tx.Delete(&models.Deployment{}, "id = ?", id).Error
- })
- }
- // List retrieves all deployments with optional filtering
- func (ds *DeploymentStore) List(filter map[string]interface{}) ([]*models.Deployment, error) {
- var deployments []*models.Deployment
- // Build query from filters
- query := ds.db.Preload("DeployedComponents")
- if filter != nil {
- for key, value := range filter {
- query = query.Where(key+" = ?", value)
- }
- }
- // Execute query
- if err := query.Find(&deployments).Error; err != nil {
- return nil, fmt.Errorf("failed to list deployments: %w", err)
- }
- // Load resources and deserialize config for each deployment
- for i, deployment := range deployments {
- // Load resources for each deployed app
- for j, app := range deployment.DeployedComponents {
- var resource models.DeployedComponentResource
- if err := ds.db.Where("deployed_app_id = ?", app.ID).First(&resource).Error; err != nil {
- if err != gorm.ErrRecordNotFound {
- return nil, fmt.Errorf("failed to get resources for deployed app: %w", err)
- }
- } else {
- deployments[i].DeployedComponents[j].Resources = models.ResourceAllocation{
- CPU: resource.CPU,
- CPUUsage: resource.CPUUsage,
- Memory: resource.Memory,
- MemoryUsage: resource.MemoryUsage,
- Storage: resource.Storage,
- StorageUsage: resource.StorageUsage,
- }
- }
- }
- // Deserialize config fields
- if err := ds.deserializeConfigFields(deployments[i]); err != nil {
- return nil, fmt.Errorf("failed to deserialize config fields: %w", err)
- }
- }
- return deployments, nil
- }
- // GetByClientID retrieves deployments for a specific client
- func (ds *DeploymentStore) GetByClientID(clientID string) ([]*models.Deployment, error) {
- return ds.List(map[string]interface{}{"client_id": clientID})
- }
- // GetByUserID retrieves deployments created by a specific user
- func (ds *DeploymentStore) GetByUserID(userID string) ([]*models.Deployment, error) {
- return ds.List(map[string]interface{}{"created_by": userID})
- }
- // GetByBlueprintID retrieves deployments based on a specific blueprint
- func (ds *DeploymentStore) GetByBlueprintID(blueprintID string) ([]*models.Deployment, error) {
- return ds.List(map[string]interface{}{"blueprint_id": blueprintID})
- }
- // serializeConfigFields serializes JSON config fields to strings
- func (ds *DeploymentStore) serializeConfigFields(deployment *models.Deployment) error {
- // Serialize logs config if provided
- if deployment.LogsConfig == "" {
- logsConfig := models.LogConfiguration{
- Enabled: true,
- RetentionDays: 7,
- }
- logsConfigBytes, err := json.Marshal(logsConfig)
- if err != nil {
- return err
- }
- deployment.LogsConfig = string(logsConfigBytes)
- }
- // Serialize metrics config if provided
- if deployment.MetricsConfig == "" {
- metricsConfig := models.MetricsConfiguration{
- Enabled: true,
- RetentionDays: 30,
- }
- metricsConfigBytes, err := json.Marshal(metricsConfig)
- if err != nil {
- return err
- }
- deployment.MetricsConfig = string(metricsConfigBytes)
- }
- // Serialize alerts config if provided
- if deployment.AlertsConfig == "" {
- alertsConfig := []models.AlertConfiguration{}
- alertsConfigBytes, err := json.Marshal(alertsConfig)
- if err != nil {
- return err
- }
- deployment.AlertsConfig = string(alertsConfigBytes)
- }
- return nil
- }
- // deserializeConfigFields deserializes JSON config fields from strings
- func (ds *DeploymentStore) deserializeConfigFields(deployment *models.Deployment) error {
- // No need to deserialize in the store, as these fields are stored as strings
- // in the database and are deserialized as needed by the service layer
- return nil
- }
|