deployment.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  1. package dbstore
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "git.linuxforward.com/byop/byop-engine/dbmanager"
  6. "git.linuxforward.com/byop/byop-engine/models"
  7. "gorm.io/gorm"
  8. )
  9. // DeploymentStore handles database operations for deployments
  10. type DeploymentStore struct {
  11. db *gorm.DB
  12. }
  13. // NewDeploymentStore creates a new DeploymentStore
  14. func NewDeploymentStore(dbManager dbmanager.DbManager) *DeploymentStore {
  15. return &DeploymentStore{
  16. db: dbManager.GetDB(),
  17. }
  18. }
  19. // Create creates a new deployment
  20. func (ds *DeploymentStore) Create(deployment *models.Deployment) error {
  21. // Ensure logs, metrics, and alerts config are properly JSON serialized
  22. if err := ds.serializeConfigFields(deployment); err != nil {
  23. return fmt.Errorf("failed to serialize config fields: %w", err)
  24. }
  25. // Create deployment in a transaction to handle deployed apps
  26. return ds.db.Transaction(func(tx *gorm.DB) error {
  27. // Create the deployment
  28. if err := tx.Create(deployment).Error; err != nil {
  29. return err
  30. }
  31. // Create any deployed apps in the same transaction
  32. for i := range deployment.DeployedComponents {
  33. app := &deployment.DeployedComponents[i]
  34. // Ensure ID is 0 so GORM can auto-generate it
  35. app.ID = 0
  36. // GORM will auto-generate the ID, just set the deployment ID relationship
  37. app.DeploymentID = deployment.ID
  38. // Create the deployed app
  39. if err := tx.Create(app).Error; err != nil {
  40. return err
  41. }
  42. // Handle resources if provided
  43. if app.Resources != (models.ResourceAllocation{}) {
  44. resource := models.DeployedAppResource{
  45. ID: 0, // Ensure ID is 0 for auto-increment
  46. DeployedAppID: app.ID,
  47. CPU: app.Resources.CPU,
  48. CPUUsage: app.Resources.CPUUsage,
  49. Memory: app.Resources.Memory,
  50. MemoryUsage: app.Resources.MemoryUsage,
  51. Storage: app.Resources.Storage,
  52. StorageUsage: app.Resources.StorageUsage,
  53. }
  54. if err := tx.Create(&resource).Error; err != nil {
  55. return err
  56. }
  57. }
  58. }
  59. return nil
  60. })
  61. }
  62. // GetByID retrieves a deployment by ID
  63. func (ds *DeploymentStore) GetByID(id int64) (*models.Deployment, error) {
  64. var deployment models.Deployment
  65. // Get deployment with all related deployed apps
  66. err := ds.db.
  67. Preload("DeployedApps").
  68. Where("rowid = ?", id). // Use SQLite's rowid for ID
  69. First(&deployment).Error
  70. if err != nil {
  71. if err == gorm.ErrRecordNotFound {
  72. return nil, nil // No deployment found
  73. }
  74. return nil, fmt.Errorf("failed to get deployment: %w", err)
  75. }
  76. // Load resources for each deployed app
  77. for i, app := range deployment.DeployedComponents {
  78. var resource models.DeployedComponentResource
  79. if err := ds.db.Where("deployed_app_id = ?", app.ID).First(&resource).Error; err != nil {
  80. if err != gorm.ErrRecordNotFound {
  81. return nil, fmt.Errorf("failed to get resources for deployed app: %w", err)
  82. }
  83. } else {
  84. deployment.DeployedComponents[i].Resources = models.ResourceAllocation{
  85. CPU: resource.CPU,
  86. CPUUsage: resource.CPUUsage,
  87. Memory: resource.Memory,
  88. MemoryUsage: resource.MemoryUsage,
  89. Storage: resource.Storage,
  90. StorageUsage: resource.StorageUsage,
  91. }
  92. }
  93. }
  94. // Deserialize config fields
  95. if err := ds.deserializeConfigFields(&deployment); err != nil {
  96. return nil, fmt.Errorf("failed to deserialize config fields: %w", err)
  97. }
  98. return &deployment, nil
  99. }
  100. // Update updates an existing deployment
  101. func (ds *DeploymentStore) Update(deployment *models.Deployment) error {
  102. // Ensure logs, metrics, and alerts config are properly JSON serialized
  103. if err := ds.serializeConfigFields(deployment); err != nil {
  104. return fmt.Errorf("failed to serialize config fields: %w", err)
  105. }
  106. // Use transaction to handle deployment and deployed apps
  107. return ds.db.Transaction(func(tx *gorm.DB) error {
  108. // Update the deployment
  109. if err := tx.Save(deployment).Error; err != nil {
  110. return err
  111. }
  112. // Handle deployed apps - this is trickier as we need to compare with existing apps
  113. var existingComponents []models.DeployedComponent
  114. if err := tx.Where("deployment_id = ?", deployment.ID).Find(&existingComponents).Error; err != nil {
  115. return err
  116. }
  117. // Create a map of existing app IDs for quick lookup
  118. existingAppMap := make(map[int64]bool)
  119. for _, app := range existingApps {
  120. existingAppMap[app.ID] = true
  121. }
  122. // Process each app in the updated deployment
  123. for i := range deployment.DeployedComponents {
  124. app := &deployment.DeployedComponents[i]
  125. // If app has ID and exists, update it
  126. if app.ID != 0 && existingAppMap[app.ID] {
  127. if err := tx.Save(app).Error; err != nil {
  128. return err
  129. }
  130. delete(existingComponentMap, app.ID)
  131. } else {
  132. // New app, create it (GORM will auto-generate ID)
  133. app.ID = 0 // Ensure ID is 0 for auto-increment
  134. app.DeploymentID = deployment.ID
  135. if err := tx.Create(app).Error; err != nil {
  136. return err
  137. }
  138. }
  139. // Handle resources
  140. if app.Resources != (models.ResourceAllocation{}) {
  141. var resource models.DeployedComponentResource
  142. result := tx.Where("deployed_app_id = ?", app.ID).First(&resource)
  143. if result.Error != nil && result.Error != gorm.ErrRecordNotFound {
  144. return result.Error
  145. }
  146. if result.Error == gorm.ErrRecordNotFound {
  147. // Create new resource (GORM will auto-generate ID)
  148. resource = models.DeployedAppResource{
  149. ID: 0, // Ensure ID is 0 for auto-increment
  150. DeployedAppID: app.ID,
  151. CPU: app.Resources.CPU,
  152. CPUUsage: app.Resources.CPUUsage,
  153. Memory: app.Resources.Memory,
  154. MemoryUsage: app.Resources.MemoryUsage,
  155. Storage: app.Resources.Storage,
  156. StorageUsage: app.Resources.StorageUsage,
  157. }
  158. if err := tx.Create(&resource).Error; err != nil {
  159. return err
  160. }
  161. } else {
  162. // Update existing resource
  163. resource.CPU = app.Resources.CPU
  164. resource.CPUUsage = app.Resources.CPUUsage
  165. resource.Memory = app.Resources.Memory
  166. resource.MemoryUsage = app.Resources.MemoryUsage
  167. resource.Storage = app.Resources.Storage
  168. resource.StorageUsage = app.Resources.StorageUsage
  169. if err := tx.Save(&resource).Error; err != nil {
  170. return err
  171. }
  172. }
  173. }
  174. }
  175. // Delete any apps that are no longer part of the deployment
  176. for appID := range existingComponentMap {
  177. if err := tx.Delete(&models.DeployedComponent{}, "id = ?", appID).Error; err != nil {
  178. return err
  179. }
  180. // Delete associated resources
  181. if err := tx.Delete(&models.DeployedComponentResource{}, "deployed_app_id = ?", appID).Error; err != nil && err != gorm.ErrRecordNotFound {
  182. return err
  183. }
  184. }
  185. return nil
  186. })
  187. }
  188. // Delete deletes a deployment by ID
  189. func (ds *DeploymentStore) Delete(id int64) error {
  190. return ds.db.Transaction(func(tx *gorm.DB) error {
  191. // Delete associated DeployedComponentResources
  192. var deployedComponents []models.DeployedComponent
  193. if err := tx.Where("deployment_id = ?", id).Find(&deployedComponents).Error; err != nil {
  194. return err
  195. }
  196. for _, app := range deployedComponents {
  197. if err := tx.Delete(&models.DeployedComponentResource{}, "deployed_app_id = ?", app.ID).Error; err != nil && err != gorm.ErrRecordNotFound {
  198. return err
  199. }
  200. }
  201. // Delete deployed apps
  202. if err := tx.Delete(&models.DeployedComponent{}, "deployment_id = ?", id).Error; err != nil && err != gorm.ErrRecordNotFound {
  203. return err
  204. }
  205. // Delete the deployment itself
  206. return tx.Delete(&models.Deployment{}, "id = ?", id).Error
  207. })
  208. }
  209. // List retrieves all deployments with optional filtering
  210. func (ds *DeploymentStore) List(filter map[string]interface{}) ([]*models.Deployment, error) {
  211. var deployments []*models.Deployment
  212. // Build query from filters
  213. query := ds.db.Preload("DeployedComponents")
  214. if filter != nil {
  215. for key, value := range filter {
  216. query = query.Where(key+" = ?", value)
  217. }
  218. }
  219. // Execute query
  220. if err := query.Find(&deployments).Error; err != nil {
  221. return nil, fmt.Errorf("failed to list deployments: %w", err)
  222. }
  223. // Load resources and deserialize config for each deployment
  224. for i, deployment := range deployments {
  225. // Load resources for each deployed app
  226. for j, app := range deployment.DeployedComponents {
  227. var resource models.DeployedComponentResource
  228. if err := ds.db.Where("deployed_app_id = ?", app.ID).First(&resource).Error; err != nil {
  229. if err != gorm.ErrRecordNotFound {
  230. return nil, fmt.Errorf("failed to get resources for deployed app: %w", err)
  231. }
  232. } else {
  233. deployments[i].DeployedComponents[j].Resources = models.ResourceAllocation{
  234. CPU: resource.CPU,
  235. CPUUsage: resource.CPUUsage,
  236. Memory: resource.Memory,
  237. MemoryUsage: resource.MemoryUsage,
  238. Storage: resource.Storage,
  239. StorageUsage: resource.StorageUsage,
  240. }
  241. }
  242. }
  243. // Deserialize config fields
  244. if err := ds.deserializeConfigFields(deployments[i]); err != nil {
  245. return nil, fmt.Errorf("failed to deserialize config fields: %w", err)
  246. }
  247. }
  248. return deployments, nil
  249. }
  250. // GetByClientID retrieves deployments for a specific client
  251. func (ds *DeploymentStore) GetByClientID(clientID int64) ([]*models.Deployment, error) {
  252. return ds.List(map[string]interface{}{"client_id": clientID})
  253. }
  254. // GetByUserID retrieves deployments created by a specific user
  255. func (ds *DeploymentStore) GetByUserID(userID string) ([]*models.Deployment, error) {
  256. return ds.List(map[string]interface{}{"created_by": userID})
  257. }
  258. // GetByAppID retrieves deployments based on a specific app (was template)
  259. func (ds *DeploymentStore) GetByAppID(appID int64) ([]*models.Deployment, error) {
  260. return ds.List(map[string]interface{}{"app_id": appID})
  261. }
  262. // GetByTemplateID is deprecated, use GetByAppID instead
  263. func (ds *DeploymentStore) GetByTemplateID(templateID int64) ([]*models.Deployment, error) {
  264. return ds.GetByAppID(templateID)
  265. }
  266. // serializeConfigFields serializes JSON config fields to strings
  267. func (ds *DeploymentStore) serializeConfigFields(deployment *models.Deployment) error {
  268. // Serialize logs config if provided
  269. if deployment.LogsConfig == "" {
  270. logsConfig := models.LogConfiguration{
  271. Enabled: true,
  272. RetentionDays: 7,
  273. }
  274. logsConfigBytes, err := json.Marshal(logsConfig)
  275. if err != nil {
  276. return err
  277. }
  278. deployment.LogsConfig = string(logsConfigBytes)
  279. }
  280. // Serialize metrics config if provided
  281. if deployment.MetricsConfig == "" {
  282. metricsConfig := models.MetricsConfiguration{
  283. Enabled: true,
  284. RetentionDays: 30,
  285. }
  286. metricsConfigBytes, err := json.Marshal(metricsConfig)
  287. if err != nil {
  288. return err
  289. }
  290. deployment.MetricsConfig = string(metricsConfigBytes)
  291. }
  292. // Serialize alerts config if provided
  293. if deployment.AlertsConfig == "" {
  294. alertsConfig := []models.AlertConfiguration{}
  295. alertsConfigBytes, err := json.Marshal(alertsConfig)
  296. if err != nil {
  297. return err
  298. }
  299. deployment.AlertsConfig = string(alertsConfigBytes)
  300. }
  301. return nil
  302. }
  303. // deserializeConfigFields deserializes JSON config fields from strings
  304. func (ds *DeploymentStore) deserializeConfigFields(deployment *models.Deployment) error {
  305. // No need to deserialize in the store, as these fields are stored as strings
  306. // in the database and are deserialized as needed by the service layer
  307. return nil
  308. }