2
0

usage.go 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. //go:build ee
  2. // +build ee
  3. package usage
  4. import (
  5. "fmt"
  6. "sync"
  7. "time"
  8. "github.com/porter-dev/porter/api/server/shared/config/env"
  9. "github.com/porter-dev/porter/api/types"
  10. "github.com/porter-dev/porter/ee/integrations/vault"
  11. "github.com/porter-dev/porter/internal/adapter"
  12. "github.com/porter-dev/porter/internal/models"
  13. "github.com/porter-dev/porter/internal/oauth"
  14. "github.com/porter-dev/porter/internal/repository"
  15. "github.com/porter-dev/porter/internal/usage"
  16. "golang.org/x/oauth2"
  17. "gorm.io/gorm"
  18. "github.com/porter-dev/porter/internal/repository/credentials"
  19. rgorm "github.com/porter-dev/porter/internal/repository/gorm"
  20. )
  21. type UsageTracker struct {
  22. db *gorm.DB
  23. repo repository.Repository
  24. doConf *oauth2.Config
  25. whitelistedUsers map[uint]uint
  26. }
  27. type UsageTrackerOpts struct {
  28. DBConf *env.DBConf
  29. DOClientID string
  30. DOClientSecret string
  31. DOScopes []string
  32. ServerURL string
  33. WhitelistedUsers map[uint]uint
  34. }
  35. const stepSize = 100
  36. func NewUsageTracker(opts *UsageTrackerOpts) (*UsageTracker, error) {
  37. db, err := adapter.New(opts.DBConf)
  38. if err != nil {
  39. return nil, err
  40. }
  41. var credBackend credentials.CredentialStorage
  42. if opts.DBConf.VaultAPIKey != "" && opts.DBConf.VaultServerURL != "" && opts.DBConf.VaultPrefix != "" {
  43. credBackend = vault.NewClient(
  44. opts.DBConf.VaultServerURL,
  45. opts.DBConf.VaultAPIKey,
  46. opts.DBConf.VaultPrefix,
  47. )
  48. }
  49. var key [32]byte
  50. for i, b := range []byte(opts.DBConf.EncryptionKey) {
  51. key[i] = b
  52. }
  53. repo := rgorm.NewRepository(db, &key, credBackend)
  54. doConf := oauth.NewDigitalOceanClient(&oauth.Config{
  55. ClientID: opts.DOClientID,
  56. ClientSecret: opts.DOClientSecret,
  57. Scopes: opts.DOScopes,
  58. BaseURL: opts.ServerURL,
  59. })
  60. return &UsageTracker{db, repo, doConf, opts.WhitelistedUsers}, nil
  61. }
  62. type UsageTrackerResponse struct {
  63. CPULimit uint
  64. CPUUsage uint
  65. MemoryLimit uint
  66. MemoryUsage uint
  67. UserLimit uint
  68. UserUsage uint
  69. ClusterLimit uint
  70. ClusterUsage uint
  71. Exceeded bool
  72. ExceededSince time.Time
  73. Project models.Project
  74. AdminEmails []string
  75. }
  76. func (u *UsageTracker) GetProjectUsage() (map[uint]*UsageTrackerResponse, error) {
  77. res := make(map[uint]*UsageTrackerResponse)
  78. // get the count of the projects
  79. var count int64
  80. if err := u.db.Model(&models.Project{}).Count(&count).Error; err != nil {
  81. return nil, err
  82. }
  83. var mu sync.Mutex
  84. var wg sync.WaitGroup
  85. worker := func(project *models.Project) {
  86. defer wg.Done()
  87. current, limit, cache, err := usage.GetUsage(&usage.GetUsageOpts{
  88. Repo: u.repo,
  89. DOConf: u.doConf,
  90. Project: project,
  91. WhitelistedUsers: u.whitelistedUsers,
  92. })
  93. if err != nil {
  94. fmt.Printf("Project %d: error getting usage: %v\n", project.ID, err)
  95. return
  96. }
  97. // get the admin emails for the project
  98. roles, err := u.repo.Project().ListProjectRoles(project.ID)
  99. if err != nil {
  100. fmt.Printf("Project %d: error getting admin emails: %v\n", project.ID, err)
  101. return
  102. }
  103. adminEmails := make([]string, 0)
  104. for _, role := range roles {
  105. if role.Kind == types.RoleAdmin {
  106. user, err := u.repo.User().ReadUser(role.UserID)
  107. if err != nil {
  108. continue
  109. }
  110. adminEmails = append(adminEmails, user.Email)
  111. }
  112. }
  113. exceededSince := cache.ExceededSince
  114. if exceededSince == nil {
  115. now := time.Now()
  116. exceededSince = &now
  117. }
  118. mu.Lock()
  119. res[project.ID] = &UsageTrackerResponse{
  120. CPUUsage: cache.ResourceCPU,
  121. CPULimit: limit.ResourceCPU,
  122. MemoryUsage: cache.ResourceMemory,
  123. MemoryLimit: limit.ResourceMemory,
  124. UserUsage: current.Users,
  125. UserLimit: limit.Users,
  126. ClusterUsage: current.Clusters,
  127. ClusterLimit: limit.Clusters,
  128. Exceeded: cache.Exceeded,
  129. ExceededSince: *exceededSince,
  130. Project: *project,
  131. AdminEmails: adminEmails,
  132. }
  133. mu.Unlock()
  134. return
  135. }
  136. // iterate (count / stepSize) + 1 times using Limit and Offset
  137. for i := 0; i < (int(count)/stepSize)+1; i++ {
  138. projects := []*models.Project{}
  139. if err := u.db.Order("id asc").Offset(i * stepSize).Limit(stepSize).Find(&projects).Error; err != nil {
  140. return nil, err
  141. }
  142. // go through each project
  143. for _, project := range projects {
  144. wg.Add(1)
  145. go worker(project)
  146. }
  147. wg.Wait()
  148. }
  149. return res, nil
  150. }