usage.go 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  1. package middleware
  2. import (
  3. "fmt"
  4. "net/http"
  5. "github.com/porter-dev/porter/api/server/shared/apierrors"
  6. "github.com/porter-dev/porter/api/server/shared/config"
  7. "github.com/porter-dev/porter/api/types"
  8. "github.com/porter-dev/porter/internal/models"
  9. "github.com/porter-dev/porter/internal/usage"
  10. )
  11. type UsageMiddleware struct {
  12. config *config.Config
  13. metric types.UsageMetric
  14. }
  15. func NewUsageMiddleware(config *config.Config, metric types.UsageMetric) *UsageMiddleware {
  16. return &UsageMiddleware{config, metric}
  17. }
  18. var UsageErrFmt = "usage limit reached for metric %s: limit %d, requested %d"
  19. func (b *UsageMiddleware) Middleware(next http.Handler) http.Handler {
  20. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  21. proj, _ := r.Context().Value(types.ProjectScope).(*models.Project)
  22. // get the project usage limits
  23. currentUsage, limit, _, err := usage.GetUsage(&usage.GetUsageOpts{
  24. Project: proj,
  25. DOConf: b.config.DOConf,
  26. Repo: b.config.Repo,
  27. WhitelistedUsers: b.config.WhitelistedUsers,
  28. })
  29. if err != nil {
  30. apierrors.HandleAPIError(
  31. b.config.Logger,
  32. b.config.Alerter,
  33. w, r,
  34. apierrors.NewErrInternal(err),
  35. true,
  36. )
  37. return
  38. }
  39. // check the usage limits
  40. allowed := allowUsage(limit, currentUsage, b.metric)
  41. if allowed {
  42. next.ServeHTTP(w, r)
  43. } else {
  44. limit, curr := getMetricUsage(limit, currentUsage, b.metric)
  45. apierrors.HandleAPIError(
  46. b.config.Logger,
  47. b.config.Alerter,
  48. w, r,
  49. apierrors.NewErrPassThroughToClient(
  50. fmt.Errorf(UsageErrFmt, b.metric, limit, curr),
  51. http.StatusBadRequest,
  52. ),
  53. true,
  54. )
  55. }
  56. })
  57. }
  58. // checkUsage returns true if the increase in usage is allowed for the given metric,
  59. // false otherwise. We only assume increments of 1 in usage for now.
  60. func allowUsage(
  61. plan, current *types.ProjectUsage,
  62. metric types.UsageMetric,
  63. ) bool {
  64. switch metric {
  65. case types.Users:
  66. return plan.Users == 0 || plan.Users >= current.Users+1
  67. case types.Clusters:
  68. return plan.Clusters == 0 || plan.Clusters >= current.Clusters+1
  69. default:
  70. return false
  71. }
  72. }
  73. func getMetricUsage(
  74. plan, current *types.ProjectUsage,
  75. metric types.UsageMetric,
  76. ) (limit uint, curr uint) {
  77. switch metric {
  78. case types.CPU:
  79. return plan.ResourceCPU, current.ResourceCPU
  80. case types.Memory:
  81. return plan.ResourceMemory, current.ResourceMemory
  82. case types.Users:
  83. return plan.Users, current.Users
  84. case types.Clusters:
  85. return plan.Clusters, current.Clusters
  86. default:
  87. return 0, 0
  88. }
  89. }