Stefan McShane преди 3 години
родител
ревизия
4184a50cf8

+ 70 - 6
api/server/handlers/porter_app/create_events.go → api/server/handlers/porter_app/create_and_update_events.go

@@ -54,27 +54,45 @@ func (p *CreateUpdatePorterAppEventHandler) ServeHTTP(w http.ResponseWriter, r *
 		p.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(e, http.StatusBadRequest))
 		return
 	}
+	telemetry.WithAttributes(span,
+		telemetry.AttributeKV{Key: "porter-app-name", Value: stackName},
+		telemetry.AttributeKV{Key: "porter-app-event-type-id", Value: request.Type},
+		telemetry.AttributeKV{Key: "porter-app-event-status", Value: request.Status},
+		telemetry.AttributeKV{Key: "porter-app-event-external-source", Value: request.TypeExternalSource},
+		telemetry.AttributeKV{Key: "porter-app-event-id", Value: request.ID},
+	)
+
+	if request.ID == "" {
+		event, err := p.createNewAppEvent(ctx, *cluster, stackName, request.Status, string(request.Type), request.TypeExternalSource, request.Metadata)
+		if err != nil {
+			e := telemetry.Error(ctx, span, err, "error creating new app event")
+			p.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(e, http.StatusBadRequest))
+			return
+		}
+		p.WriteResult(w, r, event)
+		return
+	}
 
-	event, err := p.createNewAppEvent(ctx, *cluster, stackName, request.Status, string(request.Type), request.TypeExternalSource, request.Metadata)
+	event, err := p.updateExistingAppEvent(ctx, *cluster, stackName, *request)
 	if err != nil {
 		e := telemetry.Error(ctx, span, err, "error creating new app event")
 		p.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(e, http.StatusBadRequest))
 		return
 	}
 	p.WriteResult(w, r, event)
-	return
 }
 
-func (p *CreateUpdatePorterAppEventHandler) createNewAppEvent(ctx context.Context, cluster models.Cluster, stackName string, status string, eventType string, externalSource string, requestMetadata map[string]any) (types.PorterAppEvent, error) {
+func (p *CreateUpdatePorterAppEventHandler) createNewAppEvent(ctx context.Context, cluster models.Cluster, porterAppName string, status string, eventType string, externalSource string, requestMetadata map[string]any) (types.PorterAppEvent, error) {
 	ctx, span := telemetry.NewSpan(ctx, "create-porter-app-event")
 	defer span.End()
 
-	app, err := p.Repo().PorterApp().ReadPorterAppByName(cluster.ID, stackName)
+	app, err := p.Repo().PorterApp().ReadPorterAppByName(cluster.ID, porterAppName)
 	if err != nil {
-		return types.PorterAppEvent{}, telemetry.Error(ctx, span, nil, "error retrieving porter app by name for cluster")
+		return types.PorterAppEvent{}, telemetry.Error(ctx, span, err, "error retrieving porter app by name for cluster")
 	}
 	telemetry.WithAttributes(span,
-		telemetry.AttributeKV{Key: "stack-app-name", Value: stackName},
+		telemetry.AttributeKV{Key: "porter-app-id", Value: app.ID},
+		telemetry.AttributeKV{Key: "porter-app-name", Value: porterAppName},
 		telemetry.AttributeKV{Key: "cluster-id", Value: int(cluster.ID)},
 		telemetry.AttributeKV{Key: "project-id", Value: int(cluster.ProjectID)},
 	)
@@ -103,3 +121,49 @@ func (p *CreateUpdatePorterAppEventHandler) createNewAppEvent(ctx context.Contex
 
 	return event.ToPorterAppEvent(), nil
 }
+
+func (p *CreateUpdatePorterAppEventHandler) updateExistingAppEvent(ctx context.Context, cluster models.Cluster, porterAppName string, submittedEvent types.CreateOrUpdatePorterAppEventRequest) (types.PorterAppEvent, error) {
+	ctx, span := telemetry.NewSpan(ctx, "update-porter-app-event")
+	defer span.End()
+
+	app, err := p.Repo().PorterApp().ReadPorterAppByName(cluster.ID, porterAppName)
+	if err != nil {
+		return types.PorterAppEvent{}, telemetry.Error(ctx, span, err, "error retrieving porter app by name for cluster")
+	}
+	telemetry.WithAttributes(span,
+		telemetry.AttributeKV{Key: "porter-app-id", Value: app.ID},
+		telemetry.AttributeKV{Key: "porter-app-name", Value: porterAppName},
+		telemetry.AttributeKV{Key: "cluster-id", Value: int(cluster.ID)},
+		telemetry.AttributeKV{Key: "project-id", Value: int(cluster.ProjectID)},
+	)
+
+	if submittedEvent.ID == "" {
+		return types.PorterAppEvent{}, telemetry.Error(ctx, span, nil, "porter app event id is required")
+	}
+	submittedEventID, err := uuid.Parse(submittedEvent.ID)
+	if err != nil {
+		return types.PorterAppEvent{}, telemetry.Error(ctx, span, err, "error parsing porter app event id as uuid")
+	}
+
+	existingAppEvent, err := p.Repo().PorterAppEvent().ReadEvent(ctx, submittedEventID)
+	if err != nil {
+		return types.PorterAppEvent{}, telemetry.Error(ctx, span, err, "error retrieving porter app event by id")
+	}
+
+	if submittedEvent.Status != "" {
+		existingAppEvent.Status = submittedEvent.Status
+	}
+
+	if submittedEvent.Metadata != nil {
+		for k, v := range submittedEvent.Metadata {
+			existingAppEvent.Metadata[k] = v
+		}
+	}
+
+	err = p.Repo().PorterAppEvent().UpdateEvent(ctx, &existingAppEvent)
+	if err != nil {
+		return types.PorterAppEvent{}, telemetry.Error(ctx, span, err, "error updating porter app event")
+	}
+
+	return existingAppEvent.ToPorterAppEvent(), nil
+}

+ 43 - 25
api/server/handlers/porter_app/list_events.go

@@ -80,7 +80,7 @@ func (p *PorterAppEventListHandler) ServeHTTP(w http.ResponseWriter, r *http.Req
 
 	for idx, appEvent := range porterAppEvents {
 		if appEvent.Status == "PROGRESSING" {
-			pae, err := p.updateExistingAppEvent(ctx, *cluster, stackName, appEvent.ID)
+			pae, err := p.updateExistingAppEvent(ctx, *cluster, stackName, *appEvent)
 			if err != nil {
 				e := telemetry.Error(ctx, span, nil, "unable to update existing porter app event")
 				p.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(e, http.StatusBadRequest))
@@ -108,13 +108,17 @@ func (p *PorterAppEventListHandler) ServeHTTP(w http.ResponseWriter, r *http.Req
 	p.WriteResult(w, r, res)
 }
 
-func (p *PorterAppEventListHandler) updateExistingAppEvent(ctx context.Context, cluster models.Cluster, stackName string, id uuid.UUID) (models.PorterAppEvent, error) {
+func (p *PorterAppEventListHandler) updateExistingAppEvent(ctx context.Context, cluster models.Cluster, stackName string, appEvent models.PorterAppEvent) (models.PorterAppEvent, error) {
 	ctx, span := telemetry.NewSpan(ctx, "update-porter-app-event")
 	defer span.End()
 
-	event, err := p.Repo().PorterAppEvent().ReadEvent(ctx, id)
+	if appEvent.ID == uuid.Nil {
+		return models.PorterAppEvent{}, telemetry.Error(ctx, span, nil, "porter app event id is nil when updating")
+	}
+
+	event, err := p.Repo().PorterAppEvent().ReadEvent(ctx, appEvent.ID)
 	if err != nil {
-		return models.PorterAppEvent{}, telemetry.Error(ctx, span, nil, "error retrieving porter app by name for cluster")
+		return models.PorterAppEvent{}, telemetry.Error(ctx, span, err, "error retrieving porter app by name for cluster")
 	}
 
 	telemetry.WithAttributes(span,
@@ -125,50 +129,75 @@ func (p *PorterAppEventListHandler) updateExistingAppEvent(ctx context.Context,
 		telemetry.AttributeKV{Key: "project-id", Value: int(cluster.ProjectID)},
 	)
 
+	if appEvent.Type == string(types.PorterAppEventType_Build) && appEvent.TypeExternalSource == "GITHUB" {
+		err = p.updateBuildEvent_Github(ctx, &event)
+		if err != nil {
+			return models.PorterAppEvent{}, telemetry.Error(ctx, span, err, "error updating porter app event for github build")
+		}
+	}
+
+	telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "porter-app-event-updated-status", Value: event.Status})
+
+	err = p.Repo().PorterAppEvent().UpdateEvent(ctx, &event)
+	if err != nil {
+		return models.PorterAppEvent{}, telemetry.Error(ctx, span, err, "error creating porter app event")
+	}
+
+	if event.ID == uuid.Nil {
+		return models.PorterAppEvent{}, telemetry.Error(ctx, span, nil, "porter app event not found")
+	}
+
+	return event, nil
+}
+
+func (p *PorterAppEventListHandler) updateBuildEvent_Github(ctx context.Context, event *models.PorterAppEvent) error {
+	ctx, span := telemetry.NewSpan(ctx, "update-porter-app-build-event")
+	defer span.End()
+
 	repoOrg, ok := event.Metadata["org"].(string)
 	if !ok {
-		return models.PorterAppEvent{}, telemetry.Error(ctx, span, nil, "error retrieving repo org from metadata")
+		return telemetry.Error(ctx, span, nil, "error retrieving repo org from metadata")
 	}
 
 	repoName, ok := event.Metadata["repo"].(string)
 	if !ok {
-		return models.PorterAppEvent{}, telemetry.Error(ctx, span, nil, "error retrieving repo name from metadata")
+		return telemetry.Error(ctx, span, nil, "error retrieving repo name from metadata")
 	}
 
 	actionRunIDIface, ok := event.Metadata["action_run_id"]
 	if !ok {
-		return models.PorterAppEvent{}, telemetry.Error(ctx, span, nil, "error retrieving action run id from metadata")
+		return telemetry.Error(ctx, span, nil, "error retrieving action run id from metadata")
 	}
 	actionRunID, ok := actionRunIDIface.(float64)
 	if !ok {
 		telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "action-run-id-type", Value: reflect.TypeOf(actionRunIDIface).String()})
-		return models.PorterAppEvent{}, telemetry.Error(ctx, span, nil, "error converting action run id to int")
+		return telemetry.Error(ctx, span, nil, "error converting action run id to int")
 	}
 
 	accountIDIface, ok := event.Metadata["github_account_id"]
 	if !ok {
-		return models.PorterAppEvent{}, telemetry.Error(ctx, span, nil, "error retrieving github account id from metadata")
+		return telemetry.Error(ctx, span, nil, "error retrieving github account id from metadata")
 	}
 	githubAccountID, ok := accountIDIface.(float64)
 	if !ok {
 		telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "github-account-id-type", Value: reflect.TypeOf(accountIDIface).String()})
-		return models.PorterAppEvent{}, telemetry.Error(ctx, span, nil, "error converting github account id to int")
+		return telemetry.Error(ctx, span, nil, "error converting github account id to int")
 	}
 
 	// read the environment to get the environment id
 	env, err := p.Repo().GithubAppInstallation().ReadGithubAppInstallationByAccountID(int64(githubAccountID))
 	if err != nil {
-		return models.PorterAppEvent{}, telemetry.Error(ctx, span, err, "error reading github environment by owner repo name")
+		return telemetry.Error(ctx, span, err, "error reading github environment by owner repo name")
 	}
 
 	ghClient, err := getGithubClientFromEnvironment(p.Config(), env.InstallationID)
 	if err != nil {
-		return models.PorterAppEvent{}, telemetry.Error(ctx, span, err, "error getting github client using porter application")
+		return telemetry.Error(ctx, span, err, "error getting github client using porter application")
 	}
 
 	actionRun, _, err := ghClient.Actions.GetWorkflowRunByID(ctx, repoOrg, repoName, int64(actionRunID))
 	if err != nil {
-		return models.PorterAppEvent{}, telemetry.Error(ctx, span, err, "error getting github action run by id")
+		return telemetry.Error(ctx, span, err, "error getting github action run by id")
 	}
 
 	if *actionRun.Status == "completed" {
@@ -179,18 +208,7 @@ func (p *PorterAppEventListHandler) updateExistingAppEvent(ctx context.Context,
 		}
 	}
 
-	telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "porter-app-event-updated-status", Value: event.Status})
-
-	err = p.Repo().PorterAppEvent().UpdateEvent(ctx, &event)
-	if err != nil {
-		return models.PorterAppEvent{}, telemetry.Error(ctx, span, err, "error creating porter app event")
-	}
-
-	if event.ID == uuid.Nil {
-		return models.PorterAppEvent{}, telemetry.Error(ctx, span, nil, "porter app event not found")
-	}
-
-	return event, nil
+	return nil
 }
 
 func getGithubClientFromEnvironment(config *config.Config, installationID int64) (*github.Client, error) {

+ 2 - 0
api/types/porter_app.go

@@ -86,6 +86,8 @@ const (
 	PorterAppEventType_Build PorterAppEventType = "BUILD"
 	// PorterAppEventType_Deploy represents a Porter Stack Deploy event which occurred through the Porter UI or CLI
 	PorterAppEventType_Deploy PorterAppEventType = "DEPLOY"
+	// PorterAppEventType_PreDeploy represents a Porter Stack Pre-deploy event which occurred through the Porter UI or CLI
+	PorterAppEventType_PreDeploy PorterAppEventType = "PRE_DEPLOY"
 	// PorterAppEventType_AppEvent represents a Porter Stack App Event which occurred whilst the application was running, such as an OutOfMemory (OOM) error
 	PorterAppEventType_AppEvent PorterAppEventType = "APP_EVENT"
 )

+ 76 - 15
cli/cmd/apply.go

@@ -3,6 +3,7 @@ package cmd
 import (
 	"context"
 	"encoding/json"
+	"errors"
 	"fmt"
 	"io/ioutil"
 	"net/url"
@@ -10,6 +11,7 @@ import (
 	"path/filepath"
 	"strconv"
 	"strings"
+	"time"
 
 	"github.com/cli/cli/git"
 	"github.com/fatih/color"
@@ -387,10 +389,11 @@ func (d *DeployDriver) ShouldApply(_ *switchboardModels.Resource) bool {
 }
 
 func (d *DeployDriver) Apply(resource *switchboardModels.Resource) (*switchboardModels.Resource, error) {
+	ctx := context.Background()
 	client := config.GetAPIClient()
 
 	_, err := client.GetRelease(
-		context.Background(),
+		ctx,
 		d.target.Project,
 		d.target.Cluster,
 		d.target.Namespace,
@@ -404,7 +407,7 @@ func (d *DeployDriver) Apply(resource *switchboardModels.Resource) (*switchboard
 	}
 
 	if d.source.IsApplication {
-		return d.applyApplication(resource, client, shouldCreate)
+		return d.applyApplication(ctx, resource, client, shouldCreate)
 	}
 
 	return d.applyAddon(resource, client, shouldCreate)
@@ -465,7 +468,7 @@ func (d *DeployDriver) applyAddon(resource *switchboardModels.Resource, client *
 	return resource, nil
 }
 
-func (d *DeployDriver) applyApplication(resource *switchboardModels.Resource, client *api.Client, shouldCreate bool) (*switchboardModels.Resource, error) {
+func (d *DeployDriver) applyApplication(ctx context.Context, resource *switchboardModels.Resource, client *api.Client, shouldCreate bool) (*switchboardModels.Resource, error) {
 	if resource == nil {
 		return nil, fmt.Errorf("nil resource")
 	}
@@ -555,29 +558,87 @@ func (d *DeployDriver) applyApplication(resource *switchboardModels.Resource, cl
 	if d.source.Name == "job" && appConfig.WaitForJob && (shouldCreate || !appConfig.OnlyCreate) {
 		color.New(color.FgYellow).Printf("Waiting for job '%s' to finish\n", resourceName)
 
+		var predeployEventResponseID string
+
+		stackNameWithoutRelease := strings.TrimSuffix(d.target.AppName, "-r")
+
+		if strings.Contains(d.target.Namespace, "porter-stack-") {
+			eventRequest := types.CreateOrUpdatePorterAppEventRequest{
+				Status: "PROGRESSING",
+				Type:   types.PorterAppEventType_PreDeploy,
+				Metadata: map[string]any{
+					"start_time": time.Now().UTC(),
+				},
+			}
+			eventResponse, err := client.CreateOrUpdatePorterAppEvent(ctx, d.target.Project, d.target.Cluster, stackNameWithoutRelease, &eventRequest)
+			if err != nil {
+				return nil, fmt.Errorf("error creating porter app event for pre-deploy job: %s", err.Error())
+			}
+			predeployEventResponseID = eventResponse.ID
+		}
+
 		err = wait.WaitForJob(client, &wait.WaitOpts{
 			ProjectID: d.target.Project,
 			ClusterID: d.target.Cluster,
 			Namespace: d.target.Namespace,
 			Name:      resourceName,
 		})
+		if err != nil {
+			if strings.Contains(d.target.Namespace, "porter-stack-") {
+				if predeployEventResponseID == "" {
+					return nil, errors.New("unable to find pre-deploy event response ID for failed pre-deploy event")
+				}
 
-		if err != nil && appConfig.OnlyCreate {
-			deleteJobErr := client.DeleteRelease(
-				context.Background(),
-				d.target.Project,
-				d.target.Cluster,
-				d.target.Namespace,
-				resourceName,
-			)
+				eventRequest := types.CreateOrUpdatePorterAppEventRequest{
+					ID:     predeployEventResponseID,
+					Status: "FAILED",
+					Type:   types.PorterAppEventType_PreDeploy,
+					Metadata: map[string]any{
+						"end_time": time.Now().UTC(),
+					},
+				}
+				_, err := client.CreateOrUpdatePorterAppEvent(ctx, d.target.Project, d.target.Cluster, stackNameWithoutRelease, &eventRequest)
+				if err != nil {
+					return nil, fmt.Errorf("error updating failed porter app event for pre-deploy job: %s", err.Error())
+				}
+			}
+
+			if appConfig.OnlyCreate {
+				deleteJobErr := client.DeleteRelease(
+					context.Background(),
+					d.target.Project,
+					d.target.Cluster,
+					d.target.Namespace,
+					resourceName,
+				)
 
-			if deleteJobErr != nil {
-				return nil, fmt.Errorf("error deleting job %s with waitForJob and onlyCreate set to true: %w",
-					resourceName, deleteJobErr)
+				if deleteJobErr != nil {
+					return nil, fmt.Errorf("error deleting job %s with waitForJob and onlyCreate set to true: %w",
+						resourceName, deleteJobErr)
+				}
 			}
-		} else if err != nil {
 			return nil, fmt.Errorf("error waiting for job %s: %w", resourceName, err)
 		}
+
+		if strings.Contains(d.target.Namespace, "porter-stack-") {
+			stackNameWithoutRelease := strings.TrimSuffix(d.target.AppName, "-r")
+			if predeployEventResponseID == "" {
+				return nil, errors.New("unable to find pre-deploy event response ID for successful pre-deploy event")
+			}
+			eventRequest := types.CreateOrUpdatePorterAppEventRequest{
+				ID:     predeployEventResponseID,
+				Status: "SUCCESS",
+				Type:   types.PorterAppEventType_PreDeploy,
+				Metadata: map[string]any{
+					"end_time": time.Now().UTC(),
+				},
+			}
+			_, err := client.CreateOrUpdatePorterAppEvent(ctx, d.target.Project, d.target.Cluster, stackNameWithoutRelease, &eventRequest)
+			if err != nil {
+				return nil, fmt.Errorf("error updating successful porter app event for pre-deploy job: %s", err.Error())
+			}
+		}
+
 	}
 
 	return resource, err

+ 4 - 3
cli/cmd/preview/update_config_driver.go

@@ -56,6 +56,8 @@ func (d *UpdateConfigDriver) ShouldApply(resource *models.Resource) bool {
 }
 
 func (d *UpdateConfigDriver) Apply(resource *models.Resource) (*models.Resource, error) {
+	ctx := context.Background()
+
 	updateConfigDriverConfig, err := d.getConfig(resource)
 	if err != nil {
 		return nil, err
@@ -66,7 +68,7 @@ func (d *UpdateConfigDriver) Apply(resource *models.Resource) (*models.Resource,
 	client := config.GetAPIClient()
 
 	_, err = client.GetRelease(
-		context.Background(),
+		ctx,
 		d.target.Project,
 		d.target.Cluster,
 		d.target.Namespace,
@@ -146,7 +148,7 @@ func (d *UpdateConfigDriver) Apply(resource *models.Resource) (*models.Resource,
 		}
 
 		err = client.CreateRepository(
-			context.Background(),
+			ctx,
 			sharedOpts.ProjectID,
 			regID,
 			&types.CreateRegistryRepositoryRequest{
@@ -190,7 +192,6 @@ func (d *UpdateConfigDriver) Apply(resource *models.Resource) (*models.Resource,
 			Namespace: d.target.Namespace,
 			Name:      d.target.AppName,
 		})
-
 		if err != nil {
 			return nil, err
 		}

+ 9 - 9
internal/repository/gorm/porter_app_event.go

@@ -57,7 +57,7 @@ func (repo *PorterAppEventRepository) CreateEvent(ctx context.Context, appEvent
 		appEvent.UpdatedAt = time.Now().UTC()
 	}
 	if appEvent.PorterAppID == 0 {
-		return errors.New("invalid porter app id supplied")
+		return errors.New("invalid porter app id supplied to create event")
 	}
 
 	if err := repo.db.Create(appEvent).Error; err != nil {
@@ -66,21 +66,21 @@ func (repo *PorterAppEventRepository) CreateEvent(ctx context.Context, appEvent
 	return nil
 }
 
+// UpdateEvent will set all values in the database to the values of the passed in appEvent
 func (repo *PorterAppEventRepository) UpdateEvent(ctx context.Context, appEvent *models.PorterAppEvent) error {
+	if appEvent.PorterAppID == 0 {
+		return errors.New("invalid porter app id supplied to update event")
+	}
+
 	if appEvent.ID == uuid.Nil {
-		appEvent.ID = uuid.New()
+		return errors.New("invalid porter app event id supplied to update event")
 	}
+
 	if appEvent.UpdatedAt.IsZero() {
 		appEvent.UpdatedAt = time.Now().UTC()
 	}
-	if appEvent.PorterAppID == 0 {
-		return errors.New("invalid porter app id supplied")
-	}
-	if appEvent.Status == "" {
-		return errors.New("invalid status supplied")
-	}
 
-	if err := repo.db.Model(appEvent).Updates(models.PorterAppEvent{Status: appEvent.Status}).Error; err != nil {
+	if err := repo.db.Model(appEvent).Updates(appEvent).Error; err != nil {
 		return err
 	}
 	return nil