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. "github.com/google/uuid"
  9. )
  10. // DeploymentService handles business logic for deployments
  11. type DeploymentService struct {
  12. store *dbstore.DeploymentStore
  13. appStore *dbstore.AppStore
  14. templateStore *dbstore.TemplateStore
  15. clientStore *dbstore.ClientStore
  16. }
  17. // NewDeploymentService creates a new DeploymentService
  18. func NewDeploymentService(
  19. store *dbstore.DeploymentStore,
  20. appStore *dbstore.AppStore,
  21. templateStore *dbstore.TemplateStore,
  22. clientStore *dbstore.ClientStore,
  23. ) *DeploymentService {
  24. return &DeploymentService{
  25. store: store,
  26. appStore: appStore,
  27. templateStore: templateStore,
  28. clientStore: clientStore,
  29. }
  30. }
  31. // CreateDeployment creates a new deployment
  32. func (s *DeploymentService) CreateDeployment(deployment *models.Deployment) error {
  33. // Generate UUID if not provided
  34. if deployment.ID == "" {
  35. deployment.ID = uuid.New().String()
  36. }
  37. // Validate the deployment
  38. if err := s.validateDeployment(deployment); err != nil {
  39. return fmt.Errorf("invalid deployment: %w", err)
  40. }
  41. // Set appropriate status
  42. deployment.Status = string(models.PENDING_DEPLOYMENT)
  43. // Set timestamps
  44. now := time.Now()
  45. deployment.LastDeployedAt = now
  46. // Handle deployed apps setup
  47. if err := s.setupDeployedApps(deployment); err != nil {
  48. return fmt.Errorf("failed to setup deployed apps: %w", err)
  49. }
  50. // Persist the deployment
  51. if err := s.store.Create(deployment); err != nil {
  52. return fmt.Errorf("failed to create deployment: %w", err)
  53. }
  54. // Trigger deployment process (this would normally be asynchronous)
  55. // This is a placeholder for your actual deployment logic
  56. go s.processDeployment(deployment.ID)
  57. return nil
  58. }
  59. // GetDeployment retrieves a deployment by ID
  60. func (s *DeploymentService) GetDeployment(id string) (*models.Deployment, error) {
  61. deployment, err := s.store.GetByID(id)
  62. if err != nil {
  63. return nil, fmt.Errorf("failed to retrieve deployment: %w", err)
  64. }
  65. if deployment != nil {
  66. // Deserialize config fields
  67. if err := s.deserializeConfigFields(deployment); err != nil {
  68. return nil, fmt.Errorf("failed to deserialize config fields: %w", err)
  69. }
  70. }
  71. return deployment, nil
  72. }
  73. // UpdateDeployment updates an existing deployment
  74. func (s *DeploymentService) UpdateDeployment(deployment *models.Deployment) error {
  75. // Validate the deployment ID
  76. if deployment.ID == "" {
  77. return fmt.Errorf("deployment ID is required for update")
  78. }
  79. // Check if deployment exists
  80. existingDeployment, err := s.store.GetByID(deployment.ID)
  81. if err != nil {
  82. return fmt.Errorf("failed to check if deployment exists: %w", err)
  83. }
  84. if existingDeployment == nil {
  85. return fmt.Errorf("deployment with ID %s not found", deployment.ID)
  86. }
  87. // Prevent updates to deployed apps if deployment is not in the right state
  88. if existingDeployment.Status != string(models.PENDING_DEPLOYMENT) &&
  89. existingDeployment.Status != string(models.FAILED_DEPLOYMENT) &&
  90. len(deployment.DeployedApps) > 0 {
  91. return fmt.Errorf("cannot update deployed apps when deployment is in %s state", existingDeployment.Status)
  92. }
  93. // Validate the deployment
  94. if err := s.validateDeployment(deployment); err != nil {
  95. return fmt.Errorf("invalid deployment: %w", err)
  96. }
  97. // If status was updated to "deploying", update LastDeployedAt
  98. if existingDeployment.Status != string(models.DEPLOYING) &&
  99. deployment.Status == string(models.DEPLOYING) {
  100. deployment.LastDeployedAt = time.Now()
  101. }
  102. // Handle deployed apps setup
  103. if err := s.setupDeployedApps(deployment); err != nil {
  104. return fmt.Errorf("failed to setup deployed apps: %w", err)
  105. }
  106. // Persist the deployment
  107. if err := s.store.Update(deployment); err != nil {
  108. return fmt.Errorf("failed to update deployment: %w", err)
  109. }
  110. return nil
  111. }
  112. // DeleteDeployment deletes a deployment by ID
  113. func (s *DeploymentService) DeleteDeployment(id string) error {
  114. // Check if deployment exists
  115. deployment, err := s.store.GetByID(id)
  116. if err != nil {
  117. return fmt.Errorf("failed to check if deployment exists: %w", err)
  118. }
  119. if deployment == nil {
  120. return fmt.Errorf("deployment with ID %s not found", id)
  121. }
  122. // Set status to deleting
  123. deployment.Status = string(models.DELETING)
  124. if err := s.store.Update(deployment); err != nil {
  125. return fmt.Errorf("failed to update deployment status: %w", err)
  126. }
  127. // Trigger cleanup process (this would normally be asynchronous)
  128. // This is a placeholder for your actual cleanup logic
  129. go s.processDeploymentCleanup(id)
  130. return nil
  131. }
  132. // ListDeployments retrieves all deployments with optional filtering
  133. func (s *DeploymentService) ListDeployments(filter map[string]interface{}) ([]*models.Deployment, error) {
  134. deployments, err := s.store.List(filter)
  135. if err != nil {
  136. return nil, fmt.Errorf("failed to list deployments: %w", err)
  137. }
  138. // Deserialize config fields for each deployment
  139. for _, deployment := range deployments {
  140. if err := s.deserializeConfigFields(deployment); err != nil {
  141. return nil, fmt.Errorf("failed to deserialize config fields: %w", err)
  142. }
  143. }
  144. return deployments, nil
  145. }
  146. // GetDeploymentsByClientID retrieves deployments for a specific client
  147. func (s *DeploymentService) GetDeploymentsByClientID(clientID string) ([]*models.Deployment, error) {
  148. // Check if client exists
  149. client, err := s.clientStore.GetByID(clientID)
  150. if err != nil {
  151. return nil, fmt.Errorf("failed to check if client exists: %w", err)
  152. }
  153. if client == nil {
  154. return nil, fmt.Errorf("client with ID %s not found", clientID)
  155. }
  156. deployments, err := s.store.GetByClientID(clientID)
  157. if err != nil {
  158. return nil, fmt.Errorf("failed to retrieve deployments for client %s: %w", clientID, err)
  159. }
  160. // Deserialize config fields for each deployment
  161. for _, deployment := range deployments {
  162. if err := s.deserializeConfigFields(deployment); err != nil {
  163. return nil, fmt.Errorf("failed to deserialize config fields: %w", err)
  164. }
  165. }
  166. return deployments, nil
  167. }
  168. // GetDeploymentsByUserID retrieves deployments created by a specific user
  169. func (s *DeploymentService) GetDeploymentsByUserID(userID string) ([]*models.Deployment, error) {
  170. deployments, err := s.store.GetByUserID(userID)
  171. if err != nil {
  172. return nil, fmt.Errorf("failed to retrieve deployments for user %s: %w", userID, err)
  173. }
  174. // Deserialize config fields for each deployment
  175. for _, deployment := range deployments {
  176. if err := s.deserializeConfigFields(deployment); err != nil {
  177. return nil, fmt.Errorf("failed to deserialize config fields: %w", err)
  178. }
  179. }
  180. return deployments, nil
  181. }
  182. // GetDeploymentsByTemplateID retrieves deployments based on a specific template
  183. func (s *DeploymentService) GetDeploymentsByTemplateID(templateID string) ([]*models.Deployment, error) {
  184. // Check if template exists
  185. template, err := s.templateStore.GetByID(templateID)
  186. if err != nil {
  187. return nil, fmt.Errorf("failed to check if template exists: %w", err)
  188. }
  189. if template == nil {
  190. return nil, fmt.Errorf("template with ID %s not found", templateID)
  191. }
  192. deployments, err := s.store.GetByTemplateID(templateID)
  193. if err != nil {
  194. return nil, fmt.Errorf("failed to retrieve deployments for template %s: %w", templateID, err)
  195. }
  196. // Deserialize config fields for each deployment
  197. for _, deployment := range deployments {
  198. if err := s.deserializeConfigFields(deployment); err != nil {
  199. return nil, fmt.Errorf("failed to deserialize config fields: %w", err)
  200. }
  201. }
  202. return deployments, nil
  203. }
  204. // UpdateDeploymentStatus updates the status of a deployment
  205. func (s *DeploymentService) UpdateDeploymentStatus(id string, status string) error {
  206. deployment, err := s.store.GetByID(id)
  207. if err != nil {
  208. return fmt.Errorf("failed to retrieve deployment: %w", err)
  209. }
  210. if deployment == nil {
  211. return fmt.Errorf("deployment with ID %s not found", id)
  212. }
  213. // Update the status
  214. deployment.Status = status
  215. // If status is being set to "deploying", update LastDeployedAt
  216. if status == string(models.DEPLOYING) {
  217. deployment.LastDeployedAt = time.Now()
  218. }
  219. if err := s.store.Update(deployment); err != nil {
  220. return fmt.Errorf("failed to update deployment status: %w", err)
  221. }
  222. return nil
  223. }
  224. // validateDeployment validates a deployment
  225. func (s *DeploymentService) validateDeployment(deployment *models.Deployment) error {
  226. // Validate required fields
  227. if deployment.Name == "" {
  228. return fmt.Errorf("deployment name is required")
  229. }
  230. // Validate relationships
  231. if deployment.ClientID == "" {
  232. return fmt.Errorf("client ID is required")
  233. }
  234. client, err := s.clientStore.GetByID(deployment.ClientID)
  235. if err != nil {
  236. return fmt.Errorf("failed to check client: %w", err)
  237. }
  238. if client == nil {
  239. return fmt.Errorf("client with ID %s not found", deployment.ClientID)
  240. }
  241. if deployment.TemplateID == "" {
  242. return fmt.Errorf("template ID is required")
  243. }
  244. template, err := s.templateStore.GetByID(deployment.TemplateID)
  245. if err != nil {
  246. return fmt.Errorf("failed to check template: %w", err)
  247. }
  248. if template == nil {
  249. return fmt.Errorf("template with ID %s not found", deployment.TemplateID)
  250. }
  251. return nil
  252. }
  253. // setupDeployedApps sets up deployed apps based on the template
  254. func (s *DeploymentService) setupDeployedApps(deployment *models.Deployment) error {
  255. // If deployment already has deployed apps defined, we assume they're set up correctly
  256. if len(deployment.DeployedApps) > 0 {
  257. return nil
  258. }
  259. // Get the template
  260. template, err := s.templateStore.GetByID(deployment.TemplateID)
  261. if err != nil {
  262. return fmt.Errorf("failed to retrieve template: %w", err)
  263. }
  264. if template == nil {
  265. return fmt.Errorf("template with ID %s not found", deployment.TemplateID)
  266. }
  267. // Use the template config to set up deployed apps
  268. var templateConfig models.TemplateConfig
  269. if err := json.Unmarshal([]byte(template.ConfigJSON), &templateConfig); err != nil {
  270. return fmt.Errorf("failed to parse template config: %w", err)
  271. }
  272. // Create deployed apps for each app in the template
  273. for _, appConfig := range templateConfig.Apps {
  274. // Get the app
  275. app, err := s.appStore.GetByID(appConfig.ID)
  276. if err != nil {
  277. return fmt.Errorf("failed to retrieve app: %w", err)
  278. }
  279. if app == nil {
  280. return fmt.Errorf("app with ID %s not found", appConfig.ID)
  281. }
  282. // Create a deployed app
  283. deployedApp := models.DeployedApp{
  284. ID: uuid.New().String(),
  285. DeploymentID: deployment.ID,
  286. AppID: app.ID,
  287. Status: string(models.PENDING_APP),
  288. Version: app.Version,
  289. URL: "", // Will be set during deployment
  290. PodCount: appConfig.Autoscaling.MinReplicas,
  291. HealthStatus: string(models.HEALTHY),
  292. Resources: models.ResourceAllocation{
  293. CPU: appConfig.Resources.CPU,
  294. Memory: appConfig.Resources.Memory,
  295. Storage: appConfig.Resources.Storage,
  296. },
  297. }
  298. // Add to deployment
  299. deployment.DeployedApps = append(deployment.DeployedApps, deployedApp)
  300. }
  301. return nil
  302. }
  303. // processDeployment handles the actual deployment process
  304. func (s *DeploymentService) processDeployment(deploymentID string) {
  305. // This would be an async process in a real system
  306. // For now, we just update the status after a short delay to simulate the process
  307. // Update status to deploying
  308. _ = s.UpdateDeploymentStatus(deploymentID, string(models.DEPLOYING))
  309. // In a real system, this would be where you'd:
  310. // 1. Provision infrastructure
  311. // 2. Deploy containers/apps
  312. // 3. Configure networking
  313. // 4. Setup monitoring
  314. // etc.
  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 string) {
  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. }