preview_deployments_ttl_deleter.go 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. //go:build ee
  2. package jobs
  3. import (
  4. "log"
  5. "sync"
  6. "time"
  7. "github.com/porter-dev/porter/api/server/shared/config/env"
  8. "github.com/porter-dev/porter/ee/integrations/vault"
  9. "github.com/porter-dev/porter/internal/kubernetes"
  10. "github.com/porter-dev/porter/internal/models"
  11. "github.com/porter-dev/porter/internal/oauth"
  12. "github.com/porter-dev/porter/internal/repository"
  13. rcreds "github.com/porter-dev/porter/internal/repository/credentials"
  14. rgorm "github.com/porter-dev/porter/internal/repository/gorm"
  15. "golang.org/x/oauth2"
  16. "gorm.io/gorm"
  17. "k8s.io/apimachinery/pkg/api/errors"
  18. )
  19. /*
  20. === Preview Deployments TTL Deleter Job ===
  21. This job goes through every active preview environment in all connected clusters and deletes the
  22. deployments that have exceeded their TTL, corresponding to their respective preview environment.
  23. */
  24. const (
  25. stepSize = 20
  26. )
  27. type previewDeploymentsTTLDeleter struct {
  28. enqueueTime time.Time
  29. db *gorm.DB
  30. doConf *oauth2.Config
  31. repo repository.Repository
  32. previewDeploymentsTTL string
  33. }
  34. // PreviewDeploymentsTTLDeleterOpts holds the options required to run this job
  35. type PreviewDeploymentsTTLDeleterOpts struct {
  36. DBConf *env.DBConf
  37. ServerURL string
  38. DOClientID string
  39. DOClientSecret string
  40. DOScopes []string
  41. PreviewDeploymentsTTL string
  42. }
  43. func NewPreviewDeploymentsTTLDeleter(
  44. db *gorm.DB,
  45. enqueueTime time.Time,
  46. opts *PreviewDeploymentsTTLDeleterOpts,
  47. ) (*previewDeploymentsTTLDeleter, error) {
  48. var credBackend rcreds.CredentialStorage
  49. if opts.DBConf.VaultAPIKey != "" && opts.DBConf.VaultServerURL != "" && opts.DBConf.VaultPrefix != "" {
  50. credBackend = vault.NewClient(
  51. opts.DBConf.VaultServerURL,
  52. opts.DBConf.VaultAPIKey,
  53. opts.DBConf.VaultPrefix,
  54. )
  55. }
  56. doConf := oauth.NewDigitalOceanClient(&oauth.Config{
  57. ClientID: opts.DOClientID,
  58. ClientSecret: opts.DOClientSecret,
  59. Scopes: opts.DOScopes,
  60. BaseURL: opts.ServerURL,
  61. })
  62. var key [32]byte
  63. for i, b := range []byte(opts.DBConf.EncryptionKey) {
  64. key[i] = b
  65. }
  66. repo := rgorm.NewRepository(db, &key, credBackend)
  67. return &previewDeploymentsTTLDeleter{enqueueTime, db, doConf, repo, opts.PreviewDeploymentsTTL}, nil
  68. }
  69. func (n *previewDeploymentsTTLDeleter) ID() string {
  70. return "preview-deployments-ttl-deleter"
  71. }
  72. func (n *previewDeploymentsTTLDeleter) EnqueueTime() time.Time {
  73. return n.enqueueTime
  74. }
  75. func (n *previewDeploymentsTTLDeleter) Run() error {
  76. if n.previewDeploymentsTTL == "" {
  77. log.Println("no TTL set for preview deployments, skipping job altogether")
  78. return nil
  79. }
  80. ttlDuration, err := time.ParseDuration(n.previewDeploymentsTTL)
  81. if err != nil {
  82. log.Printf("error parsing preview deployments TTL: %v. skipping job altogether", err)
  83. return nil
  84. }
  85. if ttlDuration.Hours() < 24 || ttlDuration.Hours() > 720 {
  86. log.Printf("preview deployments TTL must be between 24 (1 day) and 720 hours (30 days). skipping job altogether")
  87. return nil
  88. }
  89. var count int64
  90. if err := n.db.Model(&models.Cluster{}).Count(&count).Error; err != nil {
  91. return err
  92. }
  93. var wg sync.WaitGroup
  94. log.Println("starting deletion of preview deployments based on TTL")
  95. for i := 0; i < (int(count)/stepSize)+1; i++ {
  96. var clusters []*models.Cluster
  97. if err := n.db.Order("id asc").Offset(i * stepSize).Limit(stepSize).Find(&clusters).
  98. Error; err != nil {
  99. return err
  100. }
  101. for _, cluster := range clusters {
  102. if !cluster.PreviewEnvsEnabled {
  103. continue
  104. }
  105. envs, err := n.repo.Environment().ListEnvironments(cluster.ProjectID, cluster.ID)
  106. if err != nil {
  107. log.Printf("error listing environments for cluster %s: %v", cluster.Name, err)
  108. continue
  109. }
  110. log.Printf("found %d environments for cluster %s", len(envs), cluster.Name)
  111. for _, env := range envs {
  112. wg.Add(1)
  113. go func(env *models.Environment, cluster *models.Cluster) {
  114. defer wg.Done()
  115. depls, err := n.repo.Environment().ListDeployments(env.ID)
  116. if err != nil {
  117. log.Printf("error listing deployments for %s/%s: %v", env.GitRepoOwner, env.GitRepoName, err)
  118. return
  119. }
  120. log.Printf("found %d deployments for %s/%s", len(depls), env.GitRepoOwner, env.GitRepoName)
  121. log.Printf("deleting preview deployments based on TTL %s for %s/%s",
  122. n.previewDeploymentsTTL, env.GitRepoOwner, env.GitRepoName)
  123. k8sAgent, err := kubernetes.GetAgentOutOfClusterConfig(&kubernetes.OutOfClusterConfig{
  124. Cluster: cluster,
  125. Repo: n.repo,
  126. DigitalOceanOAuth: n.doConf,
  127. AllowInClusterConnections: false,
  128. Timeout: 10 * time.Second,
  129. })
  130. if err != nil {
  131. log.Printf("error getting k8s agent for cluster %s: %v", cluster.Name, err)
  132. return
  133. }
  134. for _, depl := range depls {
  135. // delete the deployment if it has been inactive for longer than the set TTL
  136. if depl.UpdatedAt.Add(ttlDuration).Before(time.Now()) {
  137. if depl.Namespace != "" {
  138. log.Printf("deleting namespace for deployment '%s'", depl.PRName)
  139. _, err := k8sAgent.GetNamespace(depl.Namespace)
  140. if err == nil {
  141. err := k8sAgent.DeleteNamespace(depl.Namespace)
  142. if err != nil {
  143. log.Printf("error deleting namespace for deployment '%s': %v. skipping ...",
  144. depl.PRName, err)
  145. continue
  146. }
  147. } else if !errors.IsNotFound(err) {
  148. log.Printf("error getting k8s namespace for deployment '%s': %v. skipping ...",
  149. depl.PRName, err)
  150. continue
  151. }
  152. }
  153. log.Printf("deleting deployment '%s'", depl.PRName)
  154. _, err := n.repo.Environment().DeleteDeployment(depl)
  155. if err != nil {
  156. log.Printf("error deleting deployment '%s': %v", depl.PRName, err)
  157. }
  158. }
  159. }
  160. }(env, cluster)
  161. }
  162. wg.Wait()
  163. }
  164. }
  165. log.Println("finished deletion of preview deployments based on TTL")
  166. return nil
  167. }
  168. func (n *previewDeploymentsTTLDeleter) SetData([]byte) {}