deployments.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432
  1. package services
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "time"
  6. "git.linuxforward.com/byop/byop-engine/dbstore"
  7. "git.linuxforward.com/byop/byop-engine/models"
  8. )
  9. // DeploymentService handles business logic for deployments
  10. type DeploymentService struct {
  11. store *dbstore.DeploymentStore
  12. componentStore *dbstore.ComponentStore // Renamed from appStore
  13. appStore *dbstore.AppStore // Renamed from templateStore
  14. clientStore *dbstore.ClientStore
  15. }
  16. // NewDeploymentService creates a new DeploymentService
  17. func NewDeploymentService(
  18. store *dbstore.DeploymentStore,
  19. componentStore *dbstore.ComponentStore,
  20. appStore *dbstore.AppStore,
  21. clientStore *dbstore.ClientStore,
  22. ) *DeploymentService {
  23. return &DeploymentService{
  24. store: store,
  25. componentStore: componentStore,
  26. appStore: appStore,
  27. clientStore: clientStore,
  28. }
  29. }
  30. // CreateDeployment creates a new deployment
  31. func (s *DeploymentService) CreateDeployment(deployment *models.Deployment) error {
  32. // Validate the deployment
  33. if err := s.validateDeployment(deployment); err != nil {
  34. return fmt.Errorf("invalid deployment: %w", err)
  35. }
  36. // Set appropriate status
  37. deployment.Status = string(models.PENDING_DEPLOYMENT)
  38. // Set timestamps
  39. now := time.Now()
  40. deployment.LastDeployedAt = now
  41. // Handle deployed apps setup
  42. if err := s.setupDeployedApps(deployment); err != nil {
  43. return fmt.Errorf("failed to setup deployed apps: %w", err)
  44. }
  45. // Persist the deployment
  46. if err := s.store.Create(deployment); err != nil {
  47. return fmt.Errorf("failed to create deployment: %w", err)
  48. }
  49. // Trigger deployment process (this would normally be asynchronous)
  50. // This is a placeholder for your actual deployment logic
  51. go s.processDeployment(deployment.ID)
  52. return nil
  53. }
  54. // GetDeployment retrieves a deployment by ID
  55. func (s *DeploymentService) GetDeployment(id int64) (*models.Deployment, error) {
  56. deployment, err := s.store.GetByID(id)
  57. if err != nil {
  58. return nil, fmt.Errorf("failed to retrieve deployment: %w", err)
  59. }
  60. if deployment != nil {
  61. // Deserialize config fields
  62. if err := s.deserializeConfigFields(deployment); err != nil {
  63. return nil, fmt.Errorf("failed to deserialize config fields: %w", err)
  64. }
  65. }
  66. return deployment, nil
  67. }
  68. // UpdateDeployment updates an existing deployment
  69. func (s *DeploymentService) UpdateDeployment(deployment *models.Deployment) error {
  70. // Validate the deployment ID
  71. if deployment.ID == 0 {
  72. return fmt.Errorf("deployment ID is required for update")
  73. }
  74. // Check if deployment exists
  75. existingDeployment, err := s.store.GetByID(deployment.ID)
  76. if err != nil {
  77. return fmt.Errorf("failed to check if deployment exists: %w", err)
  78. }
  79. if existingDeployment == nil {
  80. return fmt.Errorf("deployment with ID %d not found", deployment.ID)
  81. }
  82. // Prevent updates to deployed apps if deployment is not in the right state
  83. if existingDeployment.Status != string(models.PENDING_DEPLOYMENT) &&
  84. existingDeployment.Status != string(models.FAILED_DEPLOYMENT) &&
  85. len(deployment.DeployedApps) > 0 {
  86. return fmt.Errorf("cannot update deployed apps when deployment is in %s state", existingDeployment.Status)
  87. }
  88. // Validate the deployment
  89. if err := s.validateDeployment(deployment); err != nil {
  90. return fmt.Errorf("invalid deployment: %w", err)
  91. }
  92. // If status was updated to "deploying", update LastDeployedAt
  93. if existingDeployment.Status != string(models.DEPLOYING) &&
  94. deployment.Status == string(models.DEPLOYING) {
  95. deployment.LastDeployedAt = time.Now()
  96. }
  97. // Handle deployed apps setup
  98. if err := s.setupDeployedApps(deployment); err != nil {
  99. return fmt.Errorf("failed to setup deployed apps: %w", err)
  100. }
  101. // Persist the deployment
  102. if err := s.store.Update(deployment); err != nil {
  103. return fmt.Errorf("failed to update deployment: %w", err)
  104. }
  105. return nil
  106. }
  107. // DeleteDeployment deletes a deployment by ID
  108. func (s *DeploymentService) DeleteDeployment(id int64) error {
  109. // Check if deployment exists
  110. deployment, err := s.store.GetByID(id)
  111. if err != nil {
  112. return fmt.Errorf("failed to check if deployment exists: %w", err)
  113. }
  114. if deployment == nil {
  115. return fmt.Errorf("deployment with ID %d not found", id)
  116. }
  117. // Set status to deleting
  118. deployment.Status = string(models.DELETING)
  119. if err := s.store.Update(deployment); err != nil {
  120. return fmt.Errorf("failed to update deployment status: %w", err)
  121. }
  122. // Trigger cleanup process (this would normally be asynchronous)
  123. // This is a placeholder for your actual cleanup logic
  124. go s.processDeploymentCleanup(id)
  125. return nil
  126. }
  127. // ListDeployments retrieves all deployments with optional filtering
  128. func (s *DeploymentService) ListDeployments(filter map[string]interface{}) ([]*models.Deployment, error) {
  129. deployments, err := s.store.List(filter)
  130. if err != nil {
  131. return nil, fmt.Errorf("failed to list deployments: %w", err)
  132. }
  133. // Deserialize config fields for each deployment
  134. for _, deployment := range deployments {
  135. if err := s.deserializeConfigFields(deployment); err != nil {
  136. return nil, fmt.Errorf("failed to deserialize config fields: %w", err)
  137. }
  138. }
  139. return deployments, nil
  140. }
  141. // GetDeploymentsByClientID retrieves deployments for a specific client
  142. func (s *DeploymentService) GetDeploymentsByClientID(clientID int64) ([]*models.Deployment, error) {
  143. // Check if client exists
  144. client, err := s.clientStore.GetByID(clientID)
  145. if err != nil {
  146. return nil, fmt.Errorf("failed to check if client exists: %w", err)
  147. }
  148. if client == nil {
  149. return nil, fmt.Errorf("client with ID %d not found", clientID)
  150. }
  151. deployments, err := s.store.GetByClientID(clientID)
  152. if err != nil {
  153. return nil, fmt.Errorf("failed to retrieve deployments for client %s: %w", clientID, err)
  154. }
  155. // Deserialize config fields for each deployment
  156. for _, deployment := range deployments {
  157. if err := s.deserializeConfigFields(deployment); err != nil {
  158. return nil, fmt.Errorf("failed to deserialize config fields: %w", err)
  159. }
  160. }
  161. return deployments, nil
  162. }
  163. // GetDeploymentsByUserID retrieves deployments created by a specific user
  164. func (s *DeploymentService) GetDeploymentsByUserID(userID string) ([]*models.Deployment, error) {
  165. deployments, err := s.store.GetByUserID(userID)
  166. if err != nil {
  167. return nil, fmt.Errorf("failed to retrieve deployments for user %s: %w", userID, err)
  168. }
  169. // Deserialize config fields for each deployment
  170. for _, deployment := range deployments {
  171. if err := s.deserializeConfigFields(deployment); err != nil {
  172. return nil, fmt.Errorf("failed to deserialize config fields: %w", err)
  173. }
  174. }
  175. return deployments, nil
  176. }
  177. // GetDeploymentsByAppID retrieves deployments based on a specific app (was template)
  178. func (s *DeploymentService) GetDeploymentsByAppID(appID int64) ([]*models.Deployment, error) {
  179. // Check if app exists
  180. app, err := s.appStore.GetByID(appID)
  181. if err != nil {
  182. return nil, fmt.Errorf("failed to check if app exists: %w", err)
  183. }
  184. if app == nil {
  185. return nil, fmt.Errorf("app with ID %d not found", appID)
  186. }
  187. deployments, err := s.store.GetByAppID(appID)
  188. if err != nil {
  189. return nil, fmt.Errorf("failed to retrieve deployments for app %s: %w", appID, err)
  190. }
  191. // Deserialize config fields for each deployment
  192. for _, deployment := range deployments {
  193. if err := s.deserializeConfigFields(deployment); err != nil {
  194. return nil, fmt.Errorf("failed to deserialize config fields: %w", err)
  195. }
  196. }
  197. return deployments, nil
  198. }
  199. // UpdateDeploymentStatus updates the status of a deployment
  200. func (s *DeploymentService) UpdateDeploymentStatus(id int64, status string) error {
  201. deployment, err := s.store.GetByID(id)
  202. if err != nil {
  203. return fmt.Errorf("failed to retrieve deployment: %w", err)
  204. }
  205. if deployment == nil {
  206. return fmt.Errorf("deployment with ID %s not found", id)
  207. }
  208. // Update the status
  209. deployment.Status = status
  210. // If status is being set to "deploying", update LastDeployedAt
  211. if status == string(models.DEPLOYING) {
  212. deployment.LastDeployedAt = time.Now()
  213. }
  214. if err := s.store.Update(deployment); err != nil {
  215. return fmt.Errorf("failed to update deployment status: %w", err)
  216. }
  217. return nil
  218. }
  219. // validateDeployment validates a deployment
  220. func (s *DeploymentService) validateDeployment(deployment *models.Deployment) error {
  221. // Validate required fields
  222. if deployment.Name == "" {
  223. return fmt.Errorf("deployment name is required")
  224. }
  225. // Validate relationships
  226. if deployment.ClientID == 0 {
  227. return fmt.Errorf("client ID is required")
  228. }
  229. client, err := s.clientStore.GetByID(deployment.ClientID)
  230. if err != nil {
  231. return fmt.Errorf("failed to check client: %w", err)
  232. }
  233. if client == nil {
  234. return fmt.Errorf("client with ID %d not found", deployment.ClientID)
  235. }
  236. if deployment.AppID == 0 {
  237. return fmt.Errorf("app ID is required")
  238. }
  239. app, err := s.appStore.GetByID(deployment.AppID)
  240. if err != nil {
  241. return fmt.Errorf("failed to check app: %w", err)
  242. }
  243. if app == nil {
  244. return fmt.Errorf("app with ID %s not found", deployment.AppID)
  245. }
  246. return nil
  247. }
  248. // setupDeployedApps sets up deployed apps based on the template
  249. func (s *DeploymentService) setupDeployedApps(deployment *models.Deployment) error {
  250. // If deployment already has deployed apps defined, we assume they're set up correctly
  251. if len(deployment.DeployedApps) > 0 {
  252. return nil
  253. }
  254. // Get the app
  255. app, err := s.appStore.GetByID(deployment.AppID)
  256. if err != nil {
  257. return fmt.Errorf("failed to retrieve app: %w", err)
  258. }
  259. if app == nil {
  260. return fmt.Errorf("app with ID %d not found", deployment.AppID)
  261. }
  262. // Use the app config to set up deployed apps
  263. var appConfig models.AppConfig
  264. if err := json.Unmarshal([]byte(app.ConfigJSON), &appConfig); err != nil {
  265. return fmt.Errorf("failed to parse app config: %w", err)
  266. }
  267. // Create deployed apps for each component in the app
  268. for _, componentConfig := range appConfig.Components {
  269. // Get the component
  270. component, err := s.componentStore.GetByID(componentConfig.ID)
  271. if err != nil {
  272. return fmt.Errorf("failed to retrieve component: %w", err)
  273. }
  274. if component == nil {
  275. return fmt.Errorf("component with ID %d not found", componentConfig.ID)
  276. }
  277. // Create a deployed app (GORM will auto-generate ID)
  278. deployedApp := models.DeployedApp{
  279. DeploymentID: deployment.ID,
  280. ComponentID: component.ID,
  281. Status: string(models.PENDING_APP),
  282. Version: component.Version,
  283. URL: "", // Will be set during deployment
  284. PodCount: componentConfig.Autoscaling.MinReplicas,
  285. HealthStatus: string(models.HEALTHY),
  286. Resources: models.ResourceAllocation{
  287. CPU: componentConfig.Resources.CPU,
  288. Memory: componentConfig.Resources.Memory,
  289. Storage: componentConfig.Resources.Storage,
  290. },
  291. }
  292. // Add to deployment
  293. deployment.DeployedApps = append(deployment.DeployedApps, deployedApp)
  294. }
  295. return nil
  296. }
  297. // processDeployment handles the actual deployment process
  298. func (s *DeploymentService) processDeployment(deploymentID int64) {
  299. // This would be an async process in a real system
  300. // For now, we just update the status after a short delay to simulate the process
  301. // Update status to deploying
  302. _ = s.UpdateDeploymentStatus(deploymentID, string(models.DEPLOYING))
  303. // In a real system, this would be where you'd:
  304. // 1. Provision infrastructure
  305. // 2. Deploy containers/apps
  306. // 3. Configure networking
  307. // 4. Setup monitoring
  308. // etc.
  309. // Logging the deployment process
  310. fmt.Printf("Processing deployment %d...\n", deploymentID)
  311. for i := 0; i < 5; i++ {
  312. fmt.Printf("Deploying app %d/%d...\n", i+1, 5)
  313. time.Sleep(500 * time.Millisecond) // Simulate work
  314. }
  315. // For this demo, we'll just update the status after a short delay
  316. time.Sleep(2 * time.Second)
  317. // Update status to deployed or failed (randomly for demonstration)
  318. if time.Now().Unix()%2 == 0 { // Random success/failure
  319. _ = s.UpdateDeploymentStatus(deploymentID, string(models.DEPLOYED))
  320. } else {
  321. _ = s.UpdateDeploymentStatus(deploymentID, string(models.FAILED_DEPLOYMENT))
  322. }
  323. }
  324. // processDeploymentCleanup handles the cleanup process for deleted deployments
  325. func (s *DeploymentService) processDeploymentCleanup(deploymentID int64) {
  326. // This would be an async process in a real system
  327. // In a real system, this would:
  328. // 1. Deprovision infrastructure
  329. // 2. Clean up resources
  330. // 3. Remove configuration
  331. // For this demo, we'll just delete after a short delay
  332. time.Sleep(2 * time.Second)
  333. // Delete the deployment from the database
  334. _ = s.store.Delete(deploymentID)
  335. }
  336. // deserializeConfigFields deserializes JSON config fields from strings
  337. func (s *DeploymentService) deserializeConfigFields(deployment *models.Deployment) error {
  338. // Deserialize logs config
  339. if deployment.LogsConfig != "" {
  340. var logsConfig models.LogConfiguration
  341. if err := json.Unmarshal([]byte(deployment.LogsConfig), &logsConfig); err != nil {
  342. return fmt.Errorf("failed to unmarshal logs config: %w", err)
  343. }
  344. // We could set this on the deployment if needed
  345. }
  346. // Deserialize metrics config
  347. if deployment.MetricsConfig != "" {
  348. var metricsConfig models.MetricsConfiguration
  349. if err := json.Unmarshal([]byte(deployment.MetricsConfig), &metricsConfig); err != nil {
  350. return fmt.Errorf("failed to unmarshal metrics config: %w", err)
  351. }
  352. // We could set this on the deployment if needed
  353. }
  354. // Deserialize alerts config
  355. if deployment.AlertsConfig != "" {
  356. var alertsConfig []models.AlertConfiguration
  357. if err := json.Unmarshal([]byte(deployment.AlertsConfig), &alertsConfig); err != nil {
  358. return fmt.Errorf("failed to unmarshal alerts config: %w", err)
  359. }
  360. // We could set this on the deployment if needed
  361. }
  362. return nil
  363. }