notification.go 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. package notifications
  2. import (
  3. "context"
  4. "strings"
  5. "time"
  6. "github.com/google/uuid"
  7. "github.com/porter-dev/porter/internal/kubernetes"
  8. "github.com/porter-dev/porter/internal/repository"
  9. "github.com/porter-dev/porter/internal/telemetry"
  10. )
  11. type HandleNotificationInput struct {
  12. RawAgentEventMetadata map[string]any
  13. EventRepo repository.PorterAppEventRepository
  14. DeploymentTargetID string
  15. Namespace string
  16. K8sAgent *kubernetes.Agent
  17. }
  18. // HandleNotification handles the logic for processing agent events
  19. func HandleNotification(ctx context.Context, inp HandleNotificationInput) error {
  20. ctx, span := telemetry.NewSpan(ctx, "internal-handle-notification")
  21. defer span.End()
  22. // 1. parse agent event
  23. agentEventMetadata, err := parseAgentEventMetadata(inp.RawAgentEventMetadata)
  24. if err != nil {
  25. return telemetry.Error(ctx, span, err, "failed to unmarshal app event metadata")
  26. }
  27. if agentEventMetadata == nil {
  28. return telemetry.Error(ctx, span, nil, "app event metadata is nil")
  29. }
  30. // 2. convert agent event to baseNotification
  31. baseNotification := agentEventToNotification(*agentEventMetadata)
  32. // telemetry.WithAttributes(span,
  33. // telemetry.AttributeKV{Key: "app-id", Value: baseNotification.AppID},
  34. // telemetry.AttributeKV{Key: "app-name", Value: baseNotification.AppName},
  35. // telemetry.AttributeKV{Key: "service-name", Value: baseNotification.ServiceName},
  36. // telemetry.AttributeKV{Key: "app-revision-id", Value: baseNotification.AppRevisionID},
  37. // telemetry.AttributeKV{Key: "agent-event-id", Value: baseNotification.AgentEventID},
  38. // telemetry.AttributeKV{Key: "agent-detail", Value: baseNotification.AgentDetail},
  39. // )
  40. // 3. dedupe notification
  41. isDuplicate, err := isNotificationDuplicate(ctx, baseNotification, inp.EventRepo, inp.DeploymentTargetID)
  42. if err != nil {
  43. return telemetry.Error(ctx, span, err, "failed to check if app event is duplicate")
  44. }
  45. if isDuplicate {
  46. telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "is-duplicate", Value: true})
  47. return nil
  48. }
  49. telemetry.WithAttributes(span,
  50. telemetry.AttributeKV{Key: "app-id", Value: baseNotification.AppID},
  51. telemetry.AttributeKV{Key: "app-name", Value: baseNotification.AppName},
  52. telemetry.AttributeKV{Key: "service-name", Value: baseNotification.ServiceName},
  53. telemetry.AttributeKV{Key: "app-revision-id", Value: baseNotification.AppRevisionID},
  54. telemetry.AttributeKV{Key: "agent-event-id", Value: baseNotification.AgentEventID},
  55. telemetry.AttributeKV{Key: "agent-detail", Value: baseNotification.AgentDetail},
  56. )
  57. // 4. hydrate notification with k8s deployment info
  58. hydratedNotification, err := hydrateNotification(ctx, hydrateNotificationInput{
  59. Notification: baseNotification,
  60. DeploymentTargetId: inp.DeploymentTargetID,
  61. Namespace: inp.Namespace,
  62. K8sAgent: inp.K8sAgent,
  63. })
  64. if err != nil {
  65. return telemetry.Error(ctx, span, err, "failed to hydrate notification")
  66. }
  67. // 5. based on notification + k8s deployment, update the status of the matching deploy event
  68. if hydratedNotification.Deployment.Status == DeploymentStatus_Failure ||
  69. (hydratedNotification.Deployment.Status == DeploymentStatus_Pending &&
  70. detailIndicatesDeploymentFailure(hydratedNotification.AgentDetail)) {
  71. err = updateDeployEvent(ctx, updateDeployEventInput{
  72. Notification: hydratedNotification,
  73. EventRepo: inp.EventRepo,
  74. Status: PorterAppEventStatus_Failed,
  75. })
  76. if err != nil {
  77. return telemetry.Error(ctx, span, err, "failed to update deploy event matching notification")
  78. }
  79. }
  80. // 6. save notification to db
  81. // TODO: save the notification in its own table rather than co-opting the porter app events table
  82. err = saveNotification(ctx, hydratedNotification, inp.EventRepo, inp.DeploymentTargetID)
  83. if err != nil {
  84. return telemetry.Error(ctx, span, err, "failed to save notification")
  85. }
  86. return nil
  87. }
  88. type Notification struct {
  89. AppID string `json:"app_id"`
  90. AppName string `json:"app_name"`
  91. ServiceName string `json:"service_name"`
  92. AppRevisionID string `json:"app_revision_id"`
  93. AgentEventID int `json:"agent_event_id"`
  94. AgentDetail string `json:"agent_detail"`
  95. AgentSummary string `json:"agent_summary"`
  96. HumanReadableDetail string `json:"human_readable_detail"`
  97. HumanReadableSummary string `json:"human_readable_summary"`
  98. Deployment Deployment `json:"deployment"`
  99. Timestamp time.Time `json:"timestamp"`
  100. ID uuid.UUID `json:"id"`
  101. }
  102. // agentEventToNotification converts an app event to a notification
  103. func agentEventToNotification(appEventMetadata AppEventMetadata) Notification {
  104. humanReadableDetail := appEventMetadata.Detail
  105. humanReadableDetail = strings.ReplaceAll(humanReadableDetail, "application", "service")
  106. notification := Notification{
  107. AppID: appEventMetadata.AppID,
  108. AppName: appEventMetadata.AppName,
  109. ServiceName: appEventMetadata.ServiceName,
  110. AgentEventID: appEventMetadata.AgentEventID,
  111. AgentDetail: appEventMetadata.Detail,
  112. AgentSummary: appEventMetadata.Summary,
  113. AppRevisionID: appEventMetadata.AppRevisionID,
  114. Deployment: Deployment{Status: DeploymentStatus_Unknown},
  115. HumanReadableDetail: humanReadableDetail,
  116. Timestamp: time.Now().UTC(),
  117. ID: uuid.New(),
  118. }
  119. return notification
  120. }