Feroze Mohideen 2 лет назад
Родитель
Сommit
1dfa7b1d66

+ 17 - 0
api/server/handlers/porter_app/create_and_update_events.go

@@ -17,6 +17,7 @@ import (
 	"github.com/porter-dev/porter/api/server/shared/requestutils"
 	"github.com/porter-dev/porter/api/types"
 	"github.com/porter-dev/porter/internal/models"
+	"github.com/porter-dev/porter/internal/porter_app/notifications"
 	"github.com/porter-dev/porter/internal/telemetry"
 )
 
@@ -74,6 +75,22 @@ func (p *CreateUpdatePorterAppEventHandler) ServeHTTP(w http.ResponseWriter, r *
 		reportBuildStatus(ctx, request, p.Config(), user, project, appName, validateApplyV2)
 	}
 
+	// This branch will only be hit for v2 app_event type events
+	if request.ID == "" && request.DeploymentTargetID != "" && request.Type == types.PorterAppEventType_AppEvent {
+		inp := notifications.HandleNotificationInput{
+			Context:             ctx,
+			RawAppEventMetadata: request.Metadata,
+			EventRepo:           p.Repo().PorterAppEvent(),
+			DeploymentTargetID:  request.DeploymentTargetID,
+		}
+		err := notifications.HandleNotification(inp)
+		if err != nil {
+			err = telemetry.Error(ctx, span, err, "error handling notification")
+			p.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
+		}
+		return
+	}
+
 	if request.ID == "" {
 		event, err := p.createNewAppEvent(ctx, *cluster, appName, request.DeploymentTargetID, request.Status, string(request.Type), request.TypeExternalSource, request.Metadata)
 		if err != nil {

+ 98 - 0
internal/porter_app/notifications/app_event.go

@@ -0,0 +1,98 @@
+package notifications
+
+import (
+	"context"
+	"encoding/json"
+	"strconv"
+
+	"github.com/google/uuid"
+	"github.com/porter-dev/porter/internal/repository"
+	"github.com/porter-dev/porter/internal/telemetry"
+)
+
+const appEventType = "APP_EVENT"
+
+// AppEventMetadata is the metadata for an app event
+type AppEventMetadata struct {
+	// AgentEventID is the ID of the porter agent event that triggered this app event
+	AgentEventID int `json:"agent_event_id"`
+	// Revision is the revision number of the app when this event was fired
+	Revision int `json:"revision"`
+	// AppRevisionID is the revision ID of the app when this event was fired
+	AppRevisionID string `json:"app_revision_id"`
+	// ServiceName refers to the name of the service this event refers to
+	ServiceName string `json:"service_name"`
+	// ServiceType refers to the type of the service this event refers to
+	ServiceType string `json:"service_type"`
+	// ShortSummary is the short summary of the app event
+	ShortSummary string `json:"short_summary"`
+	// Summary is the summary of the app event
+	Summary string `json:"summary"`
+	// AppID is the ID of the app that this event refers to
+	AppID string `json:"app_id"`
+	// AppName is the name of the app that this event refers to
+	AppName string `json:"app_name"`
+	// Detail is the detail of the app event
+	Detail string `json:"detail"`
+}
+
+// convertMetadata converts a map of interface{} to AppEventMetadata
+func convertMetadata(metadata map[string]interface{}) (*AppEventMetadata, error) {
+	appEventMetadata := &AppEventMetadata{}
+
+	bytes, err := json.Marshal(metadata)
+	if err != nil {
+		return nil, err
+	}
+	err = json.Unmarshal(bytes, appEventMetadata)
+	if err != nil {
+		return nil, err
+	}
+
+	return appEventMetadata, nil
+}
+
+// checkIsAppEventDuplicate checks if an app event is a duplicate by seeing if another app event exists in the db with the same agent event id
+func checkIsAppEventDuplicate(
+	ctx context.Context,
+	appEventMetadata AppEventMetadata,
+	eventRepo repository.PorterAppEventRepository,
+	deploymentTargetID string,
+) (bool, error) {
+	ctx, span := telemetry.NewSpan(ctx, "is-app-event-duplicate")
+	defer span.End()
+
+	deploymentTargetUUID, err := uuid.Parse(deploymentTargetID)
+	if err != nil {
+		return false, telemetry.Error(ctx, span, err, "error parsing deployment target id")
+	}
+	if deploymentTargetUUID == uuid.Nil {
+		return false, telemetry.Error(ctx, span, nil, "deployment target id cannot be nil")
+	}
+
+	appIdInt, err := strconv.Atoi(appEventMetadata.AppID)
+	if err != nil {
+		return false, telemetry.Error(ctx, span, err, "error converting app id to int")
+	}
+	existingEvents, _, err := eventRepo.ListEventsByPorterAppIDAndDeploymentTargetID(ctx, uint(appIdInt), deploymentTargetUUID)
+	if err != nil {
+		return false, telemetry.Error(ctx, span, err, "error listing porter app events for event type with deployment target id")
+	}
+
+	for _, existingEvent := range existingEvents {
+		if existingEvent != nil && existingEvent.Type == appEventType {
+			convertedEventMetadata, err := convertMetadata(existingEvent.Metadata)
+			if err != nil || convertedEventMetadata == nil {
+				continue
+			}
+			if convertedEventMetadata.AgentEventID == 0 {
+				continue
+			}
+			if convertedEventMetadata.AgentEventID == appEventMetadata.AgentEventID {
+				return true, nil
+			}
+		}
+	}
+
+	return false, nil
+}

+ 57 - 0
internal/porter_app/notifications/notification.go

@@ -0,0 +1,57 @@
+package notifications
+
+import (
+	"context"
+
+	"github.com/porter-dev/porter/internal/repository"
+	"github.com/porter-dev/porter/internal/telemetry"
+)
+
+type HandleNotificationInput struct {
+	Context             context.Context
+	RawAppEventMetadata map[string]any
+	EventRepo           repository.PorterAppEventRepository
+	DeploymentTargetID  string
+}
+
+// HandleNotification handles the logic for processing app events (which are currently sent by the porter agent)
+func HandleNotification(inp HandleNotificationInput) error {
+	ctx, span := telemetry.NewSpan(inp.Context, "handle-notification")
+	defer span.End()
+
+	// 1. unmarshal app event
+	appEventMetadata, err := convertMetadata(inp.RawAppEventMetadata)
+	if err != nil {
+		return telemetry.Error(ctx, span, err, "failed to unmarshal app event metadata")
+	}
+	if appEventMetadata == nil {
+		return telemetry.Error(ctx, span, nil, "app event metadata is nil")
+	}
+
+	// 2. dedupe app event
+	isDuplicate, err := checkIsAppEventDuplicate(ctx, *appEventMetadata, inp.EventRepo, inp.DeploymentTargetID)
+	if err != nil {
+		return telemetry.Error(ctx, span, err, "failed to check if app event is duplicate")
+	}
+	if isDuplicate {
+		telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "is-duplicate", Value: true})
+		return nil
+	}
+
+	// 3. convert app event to notification
+	_, err = appEventToNotification(*appEventMetadata)
+	if err != nil {
+		return telemetry.Error(ctx, span, err, "failed to convert app event to notification")
+	}
+
+	// 4. based on notification, change the status of the deploy event
+	// 5. send notification
+	return nil
+}
+
+type Notification struct{}
+
+// appEventToNotification converts an app event to a notification
+func appEventToNotification(appEventMetadata AppEventMetadata) (*Notification, error) {
+	return nil, nil
+}