porter_app_event.go 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. package gorm
  2. import (
  3. "context"
  4. "encoding/json"
  5. "errors"
  6. "strconv"
  7. "time"
  8. "github.com/porter-dev/porter/internal/telemetry"
  9. "github.com/google/uuid"
  10. "github.com/porter-dev/porter/internal/models"
  11. "github.com/porter-dev/porter/internal/repository"
  12. "github.com/porter-dev/porter/internal/repository/gorm/helpers"
  13. "gorm.io/gorm"
  14. )
  15. // PorterAppEventRepository uses gorm.DB for querying the database
  16. type PorterAppEventRepository struct {
  17. db *gorm.DB
  18. }
  19. // NewPorterAppEventRepository returns a PorterAppEventRepository which uses
  20. // gorm.DB for querying the database
  21. func NewPorterAppEventRepository(db *gorm.DB) repository.PorterAppEventRepository {
  22. return &PorterAppEventRepository{db}
  23. }
  24. func (repo *PorterAppEventRepository) ListEventsByPorterAppID(ctx context.Context, porterAppID uint, opts ...helpers.QueryOption) ([]*models.PorterAppEvent, helpers.PaginatedResult, error) {
  25. apps := []*models.PorterAppEvent{}
  26. paginatedResult := helpers.PaginatedResult{}
  27. id := strconv.Itoa(int(porterAppID))
  28. if id == "" {
  29. return nil, paginatedResult, errors.New("invalid porter app id supplied")
  30. }
  31. db := repo.db.Model(&models.PorterAppEvent{})
  32. resultDB := db.Where("porter_app_id = ?", id).Order("created_at DESC")
  33. resultDB = resultDB.Scopes(helpers.Paginate(db, &paginatedResult, opts...))
  34. if err := resultDB.Find(&apps).Error; err != nil {
  35. if !errors.Is(err, gorm.ErrRecordNotFound) {
  36. return nil, paginatedResult, err
  37. }
  38. }
  39. return apps, paginatedResult, nil
  40. }
  41. // ListEventsByPorterAppIDAndDeploymentTargetID returns a list of events for a given porter app id and deployment target id
  42. func (repo *PorterAppEventRepository) ListEventsByPorterAppIDAndDeploymentTargetID(ctx context.Context, porterAppID uint, deploymentTargetID uuid.UUID, opts ...helpers.QueryOption) ([]*models.PorterAppEvent, helpers.PaginatedResult, error) {
  43. ctx, span := telemetry.NewSpan(ctx, "list-events-by-porter-app-id-and-deployment-target-id")
  44. defer span.End()
  45. telemetry.WithAttributes(span,
  46. telemetry.AttributeKV{Key: "porter-app-id", Value: porterAppID},
  47. telemetry.AttributeKV{Key: "deployment-target-id", Value: deploymentTargetID},
  48. )
  49. apps := []*models.PorterAppEvent{}
  50. paginatedResult := helpers.PaginatedResult{}
  51. id := strconv.Itoa(int(porterAppID))
  52. if id == "" {
  53. return nil, paginatedResult, telemetry.Error(ctx, span, nil, "invalid porter app id supplied")
  54. }
  55. db := repo.db.Model(&models.PorterAppEvent{})
  56. resultDB := db.Where("porter_app_id = ? AND deployment_target_id = ?", id, deploymentTargetID).Order("created_at DESC")
  57. resultDB = resultDB.Scopes(helpers.Paginate(db, &paginatedResult, opts...))
  58. if err := resultDB.Find(&apps).Error; err != nil {
  59. if !errors.Is(err, gorm.ErrRecordNotFound) {
  60. return nil, paginatedResult, telemetry.Error(ctx, span, err, "error finding events by porter app id and deployment target id")
  61. }
  62. }
  63. return apps, paginatedResult, nil
  64. }
  65. // ListBuildDeployEventsByPorterAppIDAndDeploymentTargetID returns a list of events for a given porter app id and deployment target id, withholding notification and app_event type events
  66. // This is used to display on build, pre-deploy and deploy events in the activity feed
  67. // TODO: remove this once notifications are stored in a separate table
  68. func (repo *PorterAppEventRepository) ListBuildDeployEventsByPorterAppIDAndDeploymentTargetID(ctx context.Context, porterAppID uint, deploymentTargetID uuid.UUID, opts ...helpers.QueryOption) ([]*models.PorterAppEvent, helpers.PaginatedResult, error) {
  69. ctx, span := telemetry.NewSpan(ctx, "list-build-deploy-events-by-porter-app-id-and-deployment-target-id")
  70. defer span.End()
  71. telemetry.WithAttributes(span,
  72. telemetry.AttributeKV{Key: "porter-app-id", Value: porterAppID},
  73. telemetry.AttributeKV{Key: "deployment-target-id", Value: deploymentTargetID},
  74. )
  75. apps := []*models.PorterAppEvent{}
  76. paginatedResult := helpers.PaginatedResult{}
  77. id := strconv.Itoa(int(porterAppID))
  78. if id == "" {
  79. return nil, paginatedResult, telemetry.Error(ctx, span, nil, "invalid porter app id supplied")
  80. }
  81. db := repo.db.Model(&models.PorterAppEvent{})
  82. resultDB := db.Where("porter_app_id = ? AND deployment_target_id = ? AND type != 'APP_EVENT' AND type != 'NOTIFICATION'", id, deploymentTargetID).Order("created_at DESC")
  83. resultDB = resultDB.Scopes(helpers.Paginate(db, &paginatedResult, opts...))
  84. if err := resultDB.Find(&apps).Error; err != nil {
  85. if !errors.Is(err, gorm.ErrRecordNotFound) {
  86. return nil, paginatedResult, telemetry.Error(ctx, span, err, "error finding events by porter app id and deployment target id")
  87. }
  88. }
  89. return apps, paginatedResult, nil
  90. }
  91. func (repo *PorterAppEventRepository) CreateEvent(ctx context.Context, appEvent *models.PorterAppEvent) error {
  92. if appEvent.ID == uuid.Nil {
  93. appEvent.ID = uuid.New()
  94. }
  95. if appEvent.CreatedAt.IsZero() {
  96. appEvent.CreatedAt = time.Now().UTC()
  97. }
  98. if appEvent.UpdatedAt.IsZero() {
  99. appEvent.UpdatedAt = time.Now().UTC()
  100. }
  101. if appEvent.PorterAppID == 0 {
  102. return errors.New("invalid porter app id supplied to create event")
  103. }
  104. if err := repo.db.Create(appEvent).Error; err != nil {
  105. return err
  106. }
  107. return nil
  108. }
  109. // UpdateEvent will set all values in the database to the values of the passed in appEvent
  110. func (repo *PorterAppEventRepository) UpdateEvent(ctx context.Context, appEvent *models.PorterAppEvent) error {
  111. if appEvent.PorterAppID == 0 {
  112. return errors.New("invalid porter app id supplied to update event")
  113. }
  114. if appEvent.ID == uuid.Nil {
  115. return errors.New("invalid porter app event id supplied to update event")
  116. }
  117. if appEvent.UpdatedAt.IsZero() {
  118. appEvent.UpdatedAt = time.Now().UTC()
  119. }
  120. if err := repo.db.Model(appEvent).Updates(appEvent).Error; err != nil {
  121. return err
  122. }
  123. return nil
  124. }
  125. func (repo *PorterAppEventRepository) ReadEvent(ctx context.Context, id uuid.UUID) (models.PorterAppEvent, error) {
  126. appEvent := models.PorterAppEvent{}
  127. if id == uuid.Nil {
  128. return appEvent, errors.New("invalid porter app event id supplied")
  129. }
  130. strID := id.String()
  131. if err := repo.db.Where("id = ?", strID).First(&appEvent).Error; err != nil {
  132. return appEvent, err
  133. }
  134. return appEvent, nil
  135. }
  136. // ReadNotificationsByAppRevisionID returns a list of notifications for a given porter app instance id and app revision ID
  137. func (repo *PorterAppEventRepository) ReadNotificationsByAppRevisionID(ctx context.Context, porterAppInstanceId uuid.UUID, appRevisionId string) ([]*models.PorterAppEvent, error) {
  138. notifications := []*models.PorterAppEvent{}
  139. if appRevisionId == "" {
  140. return notifications, errors.New("invalid app revision ID supplied")
  141. }
  142. if porterAppInstanceId == uuid.Nil {
  143. return notifications, errors.New("invalid porter app instance ID supplied")
  144. }
  145. // TODO: make app_revision_id a column in porter_app_event table: https://linear.app/porter/issue/POR-2096/add-app-revision-id-column-to-porter-app-events-table
  146. if err := repo.db.Where("app_instance_id = ? AND type = 'NOTIFICATION' AND metadata->>'app_revision_id' = ?", porterAppInstanceId, appRevisionId).Find(&notifications).Error; err != nil {
  147. return notifications, err
  148. }
  149. return notifications, nil
  150. }
  151. // NotificationByID returns a notification by the notification id
  152. func (repo *PorterAppEventRepository) NotificationByID(ctx context.Context, notificationID string) (*models.PorterAppEvent, error) {
  153. notification := &models.PorterAppEvent{}
  154. // TODO: make app_revision_id a column in porter_app_event table: https://linear.app/porter/issue/POR-2096/add-app-revision-id-column-to-porter-app-events-table
  155. if err := repo.db.Where("type = 'NOTIFICATION' AND metadata->>'id' = ?", notificationID).Find(&notification).Error; err != nil {
  156. return notification, err
  157. }
  158. return notification, nil
  159. }
  160. func (repo *PorterAppEventRepository) ReadDeployEventByRevision(ctx context.Context, porterAppID uint, revision float64) (models.PorterAppEvent, error) {
  161. appEvent := models.PorterAppEvent{}
  162. if porterAppID == 0 {
  163. return appEvent, errors.New("invalid porter app ID supplied")
  164. }
  165. // Convert porterAppID to string
  166. strAppID := strconv.Itoa(int(porterAppID))
  167. // Convert revision to JSON number string
  168. revJSON, err := json.Marshal(revision)
  169. if err != nil {
  170. return appEvent, errors.New("unable to marshal revision")
  171. }
  172. strRevision := string(revJSON)
  173. if err := repo.db.Where("porter_app_id = ? AND type = 'DEPLOY' AND metadata->>'revision' = ?", strAppID, strRevision).First(&appEvent).Error; err != nil {
  174. return appEvent, err
  175. }
  176. return appEvent, nil
  177. }
  178. // ReadDeployEventByAppRevisionID returns a deploy event for a given porter app id and app revision ID
  179. func (repo *PorterAppEventRepository) ReadDeployEventByAppRevisionID(ctx context.Context, porterAppID uint, appRevisionID string) (models.PorterAppEvent, error) {
  180. appEvent := models.PorterAppEvent{}
  181. if porterAppID == 0 {
  182. return appEvent, errors.New("invalid porter app ID supplied")
  183. }
  184. if appRevisionID == "" {
  185. return appEvent, errors.New("no app revision ID supplied")
  186. }
  187. // Convert porterAppID to string
  188. strAppID := strconv.Itoa(int(porterAppID))
  189. if err := repo.db.Where("porter_app_id = ? AND type = 'DEPLOY' AND metadata->>'app_revision_id' = ?", strAppID, appRevisionID).First(&appEvent).Error; err != nil {
  190. return appEvent, err
  191. }
  192. return appEvent, nil
  193. }