| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215 |
- //go:build ee
- package jobs
- import (
- "context"
- "log"
- "sync"
- "time"
- "github.com/porter-dev/porter/api/server/shared/config/env"
- "github.com/porter-dev/porter/ee/integrations/vault"
- "github.com/porter-dev/porter/internal/kubernetes"
- "github.com/porter-dev/porter/internal/models"
- "github.com/porter-dev/porter/internal/oauth"
- "github.com/porter-dev/porter/internal/repository"
- rcreds "github.com/porter-dev/porter/internal/repository/credentials"
- rgorm "github.com/porter-dev/porter/internal/repository/gorm"
- "golang.org/x/oauth2"
- "gorm.io/gorm"
- "k8s.io/apimachinery/pkg/api/errors"
- )
- /*
- === Preview Deployments TTL Deleter Job ===
- This job goes through every active preview environment in all connected clusters and deletes the
- deployments that have exceeded their TTL, corresponding to their respective preview environment.
- */
- const (
- stepSize = 20
- )
- type previewDeploymentsTTLDeleter struct {
- enqueueTime time.Time
- db *gorm.DB
- doConf *oauth2.Config
- repo repository.Repository
- previewDeploymentsTTL string
- }
- // PreviewDeploymentsTTLDeleterOpts holds the options required to run this job
- type PreviewDeploymentsTTLDeleterOpts struct {
- DBConf *env.DBConf
- ServerURL string
- DOClientID string
- DOClientSecret string
- DOScopes []string
- PreviewDeploymentsTTL string
- }
- func NewPreviewDeploymentsTTLDeleter(
- db *gorm.DB,
- enqueueTime time.Time,
- opts *PreviewDeploymentsTTLDeleterOpts,
- ) (*previewDeploymentsTTLDeleter, error) {
- var credBackend rcreds.CredentialStorage
- if opts.DBConf.VaultAPIKey != "" && opts.DBConf.VaultServerURL != "" && opts.DBConf.VaultPrefix != "" {
- credBackend = vault.NewClient(
- opts.DBConf.VaultServerURL,
- opts.DBConf.VaultAPIKey,
- opts.DBConf.VaultPrefix,
- )
- }
- doConf := oauth.NewDigitalOceanClient(&oauth.Config{
- ClientID: opts.DOClientID,
- ClientSecret: opts.DOClientSecret,
- Scopes: opts.DOScopes,
- BaseURL: opts.ServerURL,
- })
- var key [32]byte
- for i, b := range []byte(opts.DBConf.EncryptionKey) {
- key[i] = b
- }
- repo := rgorm.NewRepository(db, &key, credBackend)
- return &previewDeploymentsTTLDeleter{enqueueTime, db, doConf, repo, opts.PreviewDeploymentsTTL}, nil
- }
- func (n *previewDeploymentsTTLDeleter) ID() string {
- return "preview-deployments-ttl-deleter"
- }
- func (n *previewDeploymentsTTLDeleter) EnqueueTime() time.Time {
- return n.enqueueTime
- }
- func (n *previewDeploymentsTTLDeleter) Run(ctx context.Context) error {
- if n.previewDeploymentsTTL == "" {
- log.Println("no TTL set for preview deployments, skipping job altogether")
- return nil
- }
- ttlDuration, err := time.ParseDuration(n.previewDeploymentsTTL)
- if err != nil {
- log.Printf("error parsing preview deployments TTL: %v. skipping job altogether", err)
- return nil
- }
- if ttlDuration.Hours() < 24 || ttlDuration.Hours() > 720 {
- log.Printf("preview deployments TTL must be between 24 (1 day) and 720 hours (30 days). skipping job altogether")
- return nil
- }
- var count int64
- if err := n.db.Model(&models.Cluster{}).Count(&count).Error; err != nil {
- return err
- }
- var wg sync.WaitGroup
- log.Println("starting deletion of preview deployments based on TTL")
- for i := 0; i < (int(count)/stepSize)+1; i++ {
- var clusters []*models.Cluster
- if err := n.db.Order("id asc").Offset(i * stepSize).Limit(stepSize).Find(&clusters).
- Error; err != nil {
- return err
- }
- for _, cluster := range clusters {
- if !cluster.PreviewEnvsEnabled {
- continue
- }
- envs, err := n.repo.Environment().ListEnvironments(cluster.ProjectID, cluster.ID)
- if err != nil {
- log.Printf("error listing environments for cluster %s: %v", cluster.Name, err)
- continue
- }
- log.Printf("found %d environments for cluster %s", len(envs), cluster.Name)
- for _, env := range envs {
- wg.Add(1)
- go func(env *models.Environment, cluster *models.Cluster) {
- defer wg.Done()
- depls, err := n.repo.Environment().ListDeployments(env.ID)
- if err != nil {
- log.Printf("error listing deployments for %s/%s: %v", env.GitRepoOwner, env.GitRepoName, err)
- return
- }
- log.Printf("found %d deployments for %s/%s", len(depls), env.GitRepoOwner, env.GitRepoName)
- log.Printf("deleting preview deployments based on TTL %s for %s/%s",
- n.previewDeploymentsTTL, env.GitRepoOwner, env.GitRepoName)
- k8sAgent, err := kubernetes.GetAgentOutOfClusterConfig(ctx, &kubernetes.OutOfClusterConfig{
- Cluster: cluster,
- Repo: n.repo,
- DigitalOceanOAuth: n.doConf,
- AllowInClusterConnections: false,
- Timeout: 10 * time.Second,
- })
- if err != nil {
- log.Printf("error getting k8s agent for cluster %s: %v", cluster.Name, err)
- return
- }
- for _, depl := range depls {
- // delete the deployment if it has been inactive for longer than the set TTL
- if depl.UpdatedAt.Add(ttlDuration).Before(time.Now()) {
- if depl.Namespace != "" {
- log.Printf("deleting namespace for deployment '%s'", depl.PRName)
- _, err := k8sAgent.GetNamespace(depl.Namespace)
- if err == nil {
- err := k8sAgent.DeleteNamespace(depl.Namespace)
- if err != nil {
- log.Printf("error deleting namespace for deployment '%s': %v. skipping ...",
- depl.PRName, err)
- continue
- }
- } else if !errors.IsNotFound(err) {
- log.Printf("error getting k8s namespace for deployment '%s': %v. skipping ...",
- depl.PRName, err)
- continue
- }
- }
- log.Printf("deleting deployment '%s'", depl.PRName)
- _, err := n.repo.Environment().DeleteDeployment(depl)
- if err != nil {
- log.Printf("error deleting deployment '%s': %v", depl.PRName, err)
- }
- }
- }
- }(env, cluster)
- }
- wg.Wait()
- }
- }
- log.Println("finished deletion of preview deployments based on TTL")
- return nil
- }
- func (n *previewDeploymentsTTLDeleter) SetData([]byte) {}
|