preview_deployments_ttl_deleter.go 6.0 KB

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