Bladeren bron

Add build analytics to stacks attempt #2, pin go version to all dockerfiles using 1.20 (#3299)

Feroze Mohideen 2 jaren geleden
bovenliggende
commit
5861dbf8f7

+ 1 - 0
.github/workflows/porter_stack_porter-ui.yml

@@ -22,6 +22,7 @@ jobs:
         with:
           go-version-file: go.mod
           cache: false
+          go-version: '1.20.5'
       - name: Download Go Modules
         run: go mod download
       - name: Build Server Binary

+ 1 - 0
.github/workflows/pr_push_checks.yaml

@@ -24,6 +24,7 @@ jobs:
         with:
           go-version-file: go.mod
           cache: false
+          go-version: '1.20.5'
       - name: Run Go vet
         run: go vet ./${{ matrix.folder }}/...
       - name: Run Go tests

+ 1 - 0
.github/workflows/production.yml

@@ -22,6 +22,7 @@ jobs:
         with:
           go-version-file: go.mod
           cache: false
+          go-version: '1.20.5'
       - name: Download Go Modules
         run: go mod download
       - name: Build Server Binary

+ 38 - 9
api/server/handlers/porter_app/analytics.go

@@ -93,18 +93,47 @@ func (v *PorterAppAnalyticsHandler) ServeHTTP(w http.ResponseWriter, r *http.Req
 	v.WriteResult(w, r, user.ToUserType())
 }
 
-func TrackStackBuildFailure(
+func TrackStackBuildStatus(
 	config *config.Config,
 	user *models.User,
 	project *models.Project,
 	stackName string,
+	errorMessage string,
+	status string,
 ) error {
-	return config.AnalyticsClient.Track(analytics.StackBuildFailureTrack(&analytics.StackBuildFailureOpts{
-		ProjectScopedTrackOpts: analytics.GetProjectScopedTrackOpts(user.ID, project.ID),
-		StackName:              stackName,
-		Email:                  user.Email,
-		FirstName:              user.FirstName,
-		LastName:               user.LastName,
-		CompanyName:            user.CompanyName,
-	}))
+	if status == "PROGRESSING" {
+		return config.AnalyticsClient.Track(analytics.StackBuildProgressingTrack(&analytics.StackBuildOpts{
+			ProjectScopedTrackOpts: analytics.GetProjectScopedTrackOpts(user.ID, project.ID),
+			StackName:              stackName,
+			Email:                  user.Email,
+			FirstName:              user.FirstName,
+			LastName:               user.LastName,
+			CompanyName:            user.CompanyName,
+		}))
+	}
+
+	if status == "SUCCESS" {
+		return config.AnalyticsClient.Track(analytics.StackBuildSuccessTrack(&analytics.StackBuildOpts{
+			ProjectScopedTrackOpts: analytics.GetProjectScopedTrackOpts(user.ID, project.ID),
+			StackName:              stackName,
+			Email:                  user.Email,
+			FirstName:              user.FirstName,
+			LastName:               user.LastName,
+			CompanyName:            user.CompanyName,
+		}))
+	}
+
+	if status == "FAILED" {
+		return config.AnalyticsClient.Track(analytics.StackBuildFailureTrack(&analytics.StackBuildOpts{
+			ProjectScopedTrackOpts: analytics.GetProjectScopedTrackOpts(user.ID, project.ID),
+			StackName:              stackName,
+			ErrorMessage:           errorMessage,
+			Email:                  user.Email,
+			FirstName:              user.FirstName,
+			LastName:               user.LastName,
+			CompanyName:            user.CompanyName,
+		}))
+	}
+
+	return nil
 }

+ 37 - 5
api/server/handlers/porter_app/create_and_update_events.go

@@ -2,7 +2,9 @@ package porter_app
 
 import (
 	"context"
+	"fmt"
 	"net/http"
+	"strings"
 
 	"github.com/google/uuid"
 	"github.com/porter-dev/porter/api/server/authz"
@@ -36,10 +38,8 @@ func (p *CreateUpdatePorterAppEventHandler) ServeHTTP(w http.ResponseWriter, r *
 	defer span.End()
 
 	cluster, _ := ctx.Value(types.ClusterScope).(*models.Cluster)
-	telemetry.WithAttributes(span,
-		telemetry.AttributeKV{Key: "cluster-id", Value: int(cluster.ID)},
-		telemetry.AttributeKV{Key: "project-id", Value: int(cluster.ProjectID)},
-	)
+	user, _ := ctx.Value(types.UserScope).(*models.User)
+	project, _ := ctx.Value(types.ProjectScope).(*models.Project)
 
 	request := &types.CreateOrUpdatePorterAppEventRequest{}
 	if ok := p.DecodeAndValidate(w, r, request); !ok {
@@ -56,12 +56,16 @@ func (p *CreateUpdatePorterAppEventHandler) ServeHTTP(w http.ResponseWriter, r *
 	}
 	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-type", Value: string(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.Type == types.PorterAppEventType_Build {
+		reportBuildStatus(ctx, request, p.Config(), user, project, stackName)
+	}
+
 	if request.ID == "" {
 		event, err := p.createNewAppEvent(ctx, *cluster, stackName, request.Status, string(request.Type), request.TypeExternalSource, request.Metadata)
 		if err != nil {
@@ -82,6 +86,34 @@ func (p *CreateUpdatePorterAppEventHandler) ServeHTTP(w http.ResponseWriter, r *
 	p.WriteResult(w, r, event)
 }
 
+func reportBuildStatus(ctx context.Context, request *types.CreateOrUpdatePorterAppEventRequest, config *config.Config, user *models.User, project *models.Project, stackName string) {
+	ctx, span := telemetry.NewSpan(ctx, "report-build-status")
+	defer span.End()
+
+	telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "porter-app-build-status", Value: request.Status})
+
+	var errStr string
+	if errors, ok := request.Metadata["errors"]; ok {
+		if errs, ok := errors.(map[string]interface{}); ok {
+			errStringMap := make(map[string]string)
+			for k, v := range errs {
+				if valueStr, ok := v.(string); ok {
+					errStringMap[k] = valueStr
+				}
+			}
+
+			for k, v := range errStringMap {
+				telemetry.WithAttributes(span, telemetry.AttributeKV{Key: telemetry.AttributeKey(fmt.Sprintf("resource-%s", k)), Value: v})
+				errStr += k + ": " + v + ", "
+			}
+			errStr = strings.TrimSuffix(errStr, ", ")
+			_ = telemetry.Error(ctx, span, nil, errStr)
+		}
+	}
+
+	_ = TrackStackBuildStatus(config, user, project, stackName, errStr, request.Status)
+}
+
 // createNewAppEvent will create a new app event for the given porter app name. If the app event is an agent event, then it will be created only if there is no existing event which has the agent ID. In the case that an existing event is found, that will be returned instead
 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")

+ 0 - 111
api/server/handlers/porter_app/list_events.go

@@ -3,13 +3,8 @@ package porter_app
 import (
 	"context"
 	"errors"
-	"fmt"
 	"net/http"
-	"reflect"
-	"strconv"
 
-	"github.com/bradleyfalzon/ghinstallation/v2"
-	"github.com/google/go-github/v41/github"
 	"github.com/google/uuid"
 	"github.com/gorilla/schema"
 	"github.com/porter-dev/porter/api/server/handlers"
@@ -134,13 +129,6 @@ func (p *PorterAppEventListHandler) updateExistingAppEvent(
 		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, user, project, stackName)
-		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)
@@ -154,102 +142,3 @@ func (p *PorterAppEventListHandler) updateExistingAppEvent(
 
 	return event, nil
 }
-
-func (p *PorterAppEventListHandler) updateBuildEvent_Github(
-	ctx context.Context,
-	event *models.PorterAppEvent,
-	user *models.User,
-	project *models.Project,
-	stackName string,
-) error {
-	ctx, span := telemetry.NewSpan(ctx, "update-porter-app-build-event")
-	defer span.End()
-
-	repoOrg, ok := event.Metadata["org"].(string)
-	if !ok {
-		return telemetry.Error(ctx, span, nil, "error retrieving repo org from metadata")
-	}
-
-	repoName, ok := event.Metadata["repo"].(string)
-	if !ok {
-		return telemetry.Error(ctx, span, nil, "error retrieving repo name from metadata")
-	}
-
-	actionRunIDIface, ok := event.Metadata["action_run_id"]
-	if !ok {
-		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 telemetry.Error(ctx, span, nil, "error converting action run id to int")
-	}
-
-	accountIDIface, ok := event.Metadata["github_account_id"]
-	if !ok {
-		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 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 telemetry.Error(ctx, span, err, "error reading github environment by owner repo name")
-	}
-	if env == nil {
-		return telemetry.Error(ctx, span, nil, "github environment is nil")
-	}
-
-	ghClient, err := getGithubClientFromEnvironment(p.Config(), env.InstallationID)
-	if err != nil {
-		return telemetry.Error(ctx, span, err, "error getting github client using porter application")
-	}
-	if ghClient == nil {
-		return telemetry.Error(ctx, span, nil, "github client is nil")
-	}
-
-	actionRun, _, err := ghClient.Actions.GetWorkflowRunByID(ctx, repoOrg, repoName, int64(actionRunID))
-	if err != nil {
-		return telemetry.Error(ctx, span, err, "error getting github action run by id")
-	}
-	if actionRun == nil {
-		return telemetry.Error(ctx, span, nil, "github action run is nil")
-	}
-
-	if *actionRun.Status == "completed" {
-		if *actionRun.Conclusion == "success" {
-			event.Status = "SUCCESS"
-		} else {
-			event.Status = "FAILED"
-			_ = TrackStackBuildFailure(p.Config(), user, project, stackName)
-		}
-		event.Metadata["end_time"] = actionRun.GetUpdatedAt().Time
-	}
-
-	return nil
-}
-
-func getGithubClientFromEnvironment(config *config.Config, installationID int64) (*github.Client, error) {
-	// get the github app client
-	ghAppId, err := strconv.Atoi(config.ServerConf.GithubAppID)
-	if err != nil {
-		return nil, fmt.Errorf("malformed GITHUB_APP_ID in server configuration: %w", err)
-	}
-
-	// authenticate as github app installation
-	itr, err := ghinstallation.New(
-		http.DefaultTransport,
-		int64(ghAppId),
-		installationID,
-		config.ServerConf.GithubAppSecret,
-	)
-	if err != nil {
-		return nil, fmt.Errorf("error in creating github client from preview environment: %w", err)
-	}
-
-	return github.NewClient(&http.Client{Transport: itr}), nil
-}

+ 43 - 43
cli/cmd/stack/apply.go

@@ -36,7 +36,7 @@ func CreateApplicationDeploy(client *api.Client, worker *switchboardWorker.Worke
 		return nil, fmt.Errorf("malformed application definition: %w", err)
 	}
 
-	deployStackHook := &DeployAppHook{
+	deployAppHook := &DeployAppHook{
 		Client:               client,
 		ApplicationName:      applicationName,
 		ProjectID:            cliConf.Project,
@@ -46,60 +46,63 @@ func CreateApplicationDeploy(client *api.Client, worker *switchboardWorker.Worke
 		Builder:              builder,
 	}
 
-	worker.RegisterHook("deploy-stack", deployStackHook)
-	if os.Getenv("GITHUB_RUN_ID") != "" {
-		err := createAppEvent(client, applicationName, cliConf)
-		if err != nil {
-			return nil, err
-		}
-	}
-
+	worker.RegisterHook("deploy-app", deployAppHook)
 	return resources, nil
 }
 
 // Create app event to signfy start of build
-func createAppEvent(client *api.Client, applicationName string, cliConf *config.CLIConfig) error {
-	req := &types.CreateOrUpdatePorterAppEventRequest{
-		Status:             "PROGRESSING",
-		Type:               types.PorterAppEventType_Build,
-		TypeExternalSource: "GITHUB",
-		Metadata: map[string]any{
-			"action_run_id": os.Getenv("GITHUB_RUN_ID"),
-			"org":           os.Getenv("GITHUB_REPOSITORY_OWNER"),
-		},
-	}
+func createAppEvent(client *api.Client, applicationName string, projectId, clusterId uint) (string, error) {
+	var req *types.CreateOrUpdatePorterAppEventRequest
+	if os.Getenv("GITHUB_RUN_ID") != "" {
+		req = &types.CreateOrUpdatePorterAppEventRequest{
+			Status:             "PROGRESSING",
+			Type:               types.PorterAppEventType_Build,
+			TypeExternalSource: "GITHUB",
+			Metadata: map[string]any{
+				"action_run_id": os.Getenv("GITHUB_RUN_ID"),
+				"org":           os.Getenv("GITHUB_REPOSITORY_OWNER"),
+			},
+		}
 
-	repoNameSplit := strings.Split(os.Getenv("GITHUB_REPOSITORY"), "/")
-	if len(repoNameSplit) != 2 {
-		return fmt.Errorf("unable to parse GITHUB_REPOSITORY")
-	}
-	req.Metadata["repo"] = repoNameSplit[1]
+		repoNameSplit := strings.Split(os.Getenv("GITHUB_REPOSITORY"), "/")
+		if len(repoNameSplit) != 2 {
+			return "", fmt.Errorf("unable to parse GITHUB_REPOSITORY")
+		}
+		req.Metadata["repo"] = repoNameSplit[1]
 
-	actionRunID := os.Getenv("GITHUB_RUN_ID")
-	if actionRunID != "" {
-		arid, err := strconv.Atoi(actionRunID)
-		if err != nil {
-			return fmt.Errorf("unable to parse GITHUB_RUN_ID as int: %w", err)
+		actionRunID := os.Getenv("GITHUB_RUN_ID")
+		if actionRunID != "" {
+			arid, err := strconv.Atoi(actionRunID)
+			if err != nil {
+				return "", fmt.Errorf("unable to parse GITHUB_RUN_ID as int: %w", err)
+			}
+			req.Metadata["action_run_id"] = arid
 		}
-		req.Metadata["action_run_id"] = arid
-	}
 
-	repoOwnerAccountID := os.Getenv("GITHUB_REPOSITORY_OWNER_ID")
-	if repoOwnerAccountID != "" {
-		arid, err := strconv.Atoi(repoOwnerAccountID)
-		if err != nil {
-			return fmt.Errorf("unable to parse GITHUB_REPOSITORY_OWNER_ID as int: %w", err)
+		repoOwnerAccountID := os.Getenv("GITHUB_REPOSITORY_OWNER_ID")
+		if repoOwnerAccountID != "" {
+			arid, err := strconv.Atoi(repoOwnerAccountID)
+			if err != nil {
+				return "", fmt.Errorf("unable to parse GITHUB_REPOSITORY_OWNER_ID as int: %w", err)
+			}
+			req.Metadata["github_account_id"] = arid
+		}
+	} else {
+		req = &types.CreateOrUpdatePorterAppEventRequest{
+			Status:             "PROGRESSING",
+			Type:               types.PorterAppEventType_Build,
+			TypeExternalSource: "GITHUB",
+			Metadata:           map[string]any{},
 		}
-		req.Metadata["github_account_id"] = arid
 	}
 
 	ctx := context.Background()
-	_, err := client.CreateOrUpdatePorterAppEvent(ctx, cliConf.Project, cliConf.Cluster, applicationName, req)
+	event, err := client.CreateOrUpdatePorterAppEvent(ctx, projectId, clusterId, applicationName, req)
 	if err != nil {
-		return fmt.Errorf("unable to create porter app build event: %w", err)
+		return "", fmt.Errorf("unable to create porter app build event: %w", err)
 	}
 
-	return nil
+	return event.ID, nil
 }
 
 func createV1BuildResources(client *api.Client, app *Application, stackName string, projectID uint, clusterID uint) ([]*switchboardTypes.Resource, string, error) {
@@ -118,7 +121,6 @@ func createV1BuildResources(client *api.Client, app *Application, stackName stri
 		color.New(color.FgYellow).Printf("No build values specified in porter.yaml, attempting to load stack build settings instead \n")
 
 		res, err := client.GetPorterApp(context.Background(), stackConf.projectID, stackConf.clusterID, stackConf.stackName)
-
 		if err != nil {
 			return nil, "", fmt.Errorf("unable to read build info from DB: %w", err)
 		}
@@ -147,7 +149,6 @@ func createV1BuildResources(client *api.Client, app *Application, stackName stri
 			stackConf.clusterID,
 			stackConf.parsed.Env,
 		)
-
 		if err != nil {
 			return nil, "", err
 		}
@@ -164,7 +165,6 @@ func createV1BuildResources(client *api.Client, app *Application, stackName stri
 }
 
 func createStackConf(client *api.Client, app *Application, stackName string, projectID uint, clusterID uint) (*StackConf, error) {
-
 	err := config.ValidateCLIEnvironment()
 	if err != nil {
 		errMsg := composePreviewMessage("porter CLI is not configured correctly", Error)

+ 41 - 7
cli/cmd/stack/hooks.go

@@ -19,6 +19,7 @@ type DeployAppHook struct {
 	BuildImageDriverName string
 	PorterYAML           []byte
 	Builder              string
+	BuildEventID         string
 }
 
 func (t *DeployAppHook) PreApply() error {
@@ -27,6 +28,13 @@ func (t *DeployAppHook) PreApply() error {
 		errMsg := composePreviewMessage("porter CLI is not configured correctly", Error)
 		return fmt.Errorf("%s: %w", errMsg, err)
 	}
+
+	buildEventId, err := createAppEvent(t.Client, t.ApplicationName, t.ProjectID, t.ClusterID)
+	if err != nil {
+		return err
+	}
+	t.BuildEventID = buildEventId
+
 	return nil
 }
 
@@ -39,10 +47,17 @@ func (t *DeployAppHook) DataQueries() map[string]interface{} {
 
 // deploy the app
 func (t *DeployAppHook) PostApply(driverOutput map[string]interface{}) error {
-	client := config.GetAPIClient()
+	eventRequest := types.CreateOrUpdatePorterAppEventRequest{
+		Status:   "SUCCESS",
+		Type:     types.PorterAppEventType_Build,
+		Metadata: map[string]any{},
+		ID:       t.BuildEventID,
+	}
+	_, _ = t.Client.CreateOrUpdatePorterAppEvent(context.Background(), t.ProjectID, t.ClusterID, t.ApplicationName, &eventRequest)
+
 	namespace := fmt.Sprintf("porter-stack-%s", t.ApplicationName)
 
-	_, err := client.GetRelease(
+	_, err := t.Client.GetRelease(
 		context.Background(),
 		t.ProjectID,
 		t.ClusterID,
@@ -58,10 +73,10 @@ func (t *DeployAppHook) PostApply(driverOutput map[string]interface{}) error {
 		color.New(color.FgGreen).Printf("Found release for app %s: attempting update\n", t.ApplicationName)
 	}
 
-	return t.applyApp(client, shouldCreate, driverOutput)
+	return t.applyApp(shouldCreate, driverOutput)
 }
 
-func (t *DeployAppHook) applyApp(client *api.Client, shouldCreate bool, driverOutput map[string]interface{}) error {
+func (t *DeployAppHook) applyApp(shouldCreate bool, driverOutput map[string]interface{}) error {
 	var imageInfo types.ImageInfo
 	image, ok := driverOutput["image"].(string)
 	// if it contains a $, then it means the query didn't resolve to anything
@@ -77,7 +92,7 @@ func (t *DeployAppHook) applyApp(client *api.Client, shouldCreate bool, driverOu
 		}
 	}
 
-	_, err := client.CreatePorterApp(
+	_, err := t.Client.CreatePorterApp(
 		context.Background(),
 		t.ProjectID,
 		t.ClusterID,
@@ -101,5 +116,24 @@ func (t *DeployAppHook) applyApp(client *api.Client, shouldCreate bool, driverOu
 	return nil
 }
 
-func (t *DeployAppHook) OnConsolidatedErrors(map[string]error) {}
-func (t *DeployAppHook) OnError(error)                         {}
+func (t *DeployAppHook) OnConsolidatedErrors(errors map[string]error) {
+	errorStringMap := make(map[string]string)
+	for k, v := range errors {
+		errorStringMap[k] = fmt.Sprintf("%+v", v)
+	}
+	eventRequest := types.CreateOrUpdatePorterAppEventRequest{
+		Status: "FAILED",
+		Type:   types.PorterAppEventType_Build,
+		Metadata: map[string]any{
+			"errors": errorStringMap,
+		},
+		ID: t.BuildEventID,
+	}
+	_, _ = t.Client.CreateOrUpdatePorterAppEvent(context.Background(), t.ProjectID, t.ClusterID, t.ApplicationName, &eventRequest)
+}
+
+func (t *DeployAppHook) OnError(err error) {
+	t.OnConsolidatedErrors(map[string]error{
+		"pre-apply": err,
+	})
+}

+ 3 - 2
dashboard/src/main/home/app-dashboard/expanded-app/activity-feed/ActivityFeed.tsx

@@ -20,6 +20,7 @@ import Button from "components/porter/Button";
 import Icon from "components/porter/Icon";
 import Container from "components/porter/Container";
 import EventFocusView from "./events/focus-views/EventFocusView";
+import { PorterAppEvent } from "shared/types";
 
 type Props = {
   chart: any;
@@ -31,7 +32,7 @@ type Props = {
 const ActivityFeed: React.FC<Props> = ({ chart, stackName, appData, eventId }) => {
   const { currentProject, currentCluster } = useContext(Context);
 
-  const [events, setEvents] = useState<any[]>([]);
+  const [events, setEvents] = useState<PorterAppEvent[]>([]);
   const [loading, setLoading] = useState<boolean>(true);
   const [error, setError] = useState<any>(null);
   const [page, setPage] = useState<number>(1);
@@ -59,7 +60,7 @@ const ActivityFeed: React.FC<Props> = ({ chart, stackName, appData, eventId }) =
       );
 
       setNumPages(res.data.num_pages);
-      setEvents(res.data.events);
+      setEvents(res.data.events?.map((event: any) => PorterAppEvent.toPorterAppEvent(event)) ?? []);
     } catch (err) {
       setError(err);
     } finally {

+ 1 - 81
dashboard/src/main/home/app-dashboard/expanded-app/activity-feed/events/cards/BuildEventCard.tsx

@@ -1,4 +1,4 @@
-import React, { useState } from "react";
+import React, { useEffect, useState } from "react";
 import styled from "styled-components";
 
 import build from "assets/build.png";
@@ -11,11 +11,7 @@ import Container from "components/porter/Container";
 import Spacer from "components/porter/Spacer";
 import Link from "components/porter/Link";
 import Icon from "components/porter/Icon";
-import api from "shared/api";
 import { Log } from "main/home/cluster-dashboard/expanded-chart/logs-section/useAgentLogs";
-import JSZip from "jszip";
-import Anser, { AnserJsonEntry } from "anser";
-import GHALogsModal from "../../../status/GHALogsModal";
 import { PorterAppEvent } from "shared/types";
 import { getDuration, getStatusIcon, triggerWorkflow } from '../utils';
 import { StyledEventCard } from "./EventCard";
@@ -27,9 +23,6 @@ type Props = {
 };
 
 const BuildEventCard: React.FC<Props> = ({ event, appData }) => {
-  const [logModalVisible, setLogModalVisible] = useState(false);
-  const [logs, setLogs] = useState<Log[]>([]);
-
   const renderStatusText = (event: PorterAppEvent) => {
     switch (event.status) {
       case "SUCCESS":
@@ -41,79 +34,6 @@ const BuildEventCard: React.FC<Props> = ({ event, appData }) => {
     }
   };
 
-  const getBuildLogs = async () => {
-    try {
-      setLogs([]);
-      setLogModalVisible(true);
-
-      const res = await api.getGHWorkflowLogById(
-        "",
-        {},
-        {
-          project_id: appData.app.project_id,
-          cluster_id: appData.app.cluster_id,
-          git_installation_id: appData.app.git_repo_id,
-          owner: appData.app.repo_name?.split("/")[0],
-          name: appData.app.repo_name?.split("/")[1],
-          filename: "porter_stack_" + appData.chart.name + ".yml",
-          run_id: event.metadata.action_run_id,
-        }
-      );
-      let logs: Log[] = [];
-      if (res.data != null) {
-        // Fetch the logs
-        const logsResponse = await fetch(res.data);
-
-        // Ensure that the response body is only read once
-        const logsBlob = await logsResponse.blob();
-
-        if (logsResponse.headers.get("Content-Type") === "application/zip") {
-          const zip = await JSZip.loadAsync(logsBlob);
-          const promises: any[] = [];
-
-          zip.forEach(function (relativePath, zipEntry) {
-            promises.push(
-              (async function () {
-                const fileData = await zip
-                  .file(relativePath)
-                  ?.async("string");
-
-                if (
-                  fileData &&
-                  fileData.includes("Run porter-dev/porter-cli-action@v0.1.0")
-                ) {
-                  const lines = fileData.split("\n");
-                  const timestampPattern = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d+Z/;
-
-                  lines.forEach((line, index) => {
-                    const lineWithoutTimestamp = line.replace(timestampPattern, "").trimStart();
-                    const anserLine: AnserJsonEntry[] = Anser.ansiToJson(lineWithoutTimestamp);
-                    if (lineWithoutTimestamp.toLowerCase().includes("error")) {
-                      anserLine[0].fg = "238,75,43";
-                    }
-
-                    const log: Log = {
-                      line: anserLine,
-                      lineNumber: index + 1,
-                      timestamp: line.match(timestampPattern)?.[0],
-                    };
-
-                    logs.push(log);
-                  });
-                }
-              })()
-            );
-          });
-
-          await Promise.all(promises);
-          setLogs(logs);
-        }
-      }
-    } catch (error) {
-      console.log(error);
-    }
-  };
-
   const renderInfoCta = (event: PorterAppEvent) => {
     switch (event.status) {
       case "SUCCESS":

+ 1 - 1
dashboard/src/main/home/app-dashboard/expanded-app/activity-feed/events/focus-views/EventFocusView.tsx

@@ -39,7 +39,7 @@ const EventFocusView: React.FC<Props> = ({
                         event_id: eventId,
                     }
                 )
-                const newEvent = eventResp.data.event as PorterAppEvent;
+                const newEvent = PorterAppEvent.toPorterAppEvent(eventResp.data.event);
                 setEvent(newEvent);
                 if (newEvent.metadata.end_time != null) {
                     clearInterval(intervalId);

+ 14 - 0
dashboard/src/shared/types.tsx

@@ -681,5 +681,19 @@ export interface PorterAppEvent {
   porter_app_id: number;
   metadata: any;
 }
+export const PorterAppEvent = {
+  toPorterAppEvent: (data: any): PorterAppEvent => {
+    return {
+      created_at: data.created_at ?? "",
+      updated_at: data.updated_at ?? "",
+      id: data.id ?? "",
+      status: data.status ?? "",
+      type: data.type ?? "",
+      type_source: data.type_source ?? "",
+      porter_app_id: data.porter_app_id ?? "",
+      metadata: data.metadata ?? {},
+    };
+  }
+}
 
 

+ 2 - 1
docker/Dockerfile

@@ -2,7 +2,8 @@
 
 # Base Go environment
 # -------------------
-FROM golang:1.20-alpine as base
+# pinned because of https://github.com/moby/moby/issues/45935
+FROM golang:1.20.5-alpine as base
 WORKDIR /porter
 
 RUN apk update && apk add --no-cache gcc musl-dev git protoc

+ 2 - 1
docker/cli.Dockerfile

@@ -2,7 +2,8 @@
 
 # Base Go environment
 # -------------------
-FROM golang:1.20 as base
+# pinned because of https://github.com/moby/moby/issues/45935
+FROM golang:1.20.5 as base
 WORKDIR /porter
 
 RUN apt-get update && apt-get install -y gcc musl-dev git make

+ 2 - 1
docker/dev.Dockerfile

@@ -1,6 +1,7 @@
 # Development environment
 # -----------------------
-FROM golang:1.20-alpine
+# pinned because of https://github.com/moby/moby/issues/45935
+FROM golang:1.20.5-alpine
 WORKDIR /porter
 
 RUN apk update && apk add --no-cache gcc musl-dev git

+ 2 - 1
ee/docker/ee.Dockerfile

@@ -2,7 +2,8 @@
 
 # Base Go environment
 # -------------------
-FROM golang:1.20-alpine as base
+# pinned because of https://github.com/moby/moby/issues/45935
+FROM golang:1.20.5-alpine as base
 WORKDIR /porter
 
 RUN apk update && apk add --no-cache gcc musl-dev git protoc

+ 2 - 1
ee/docker/provisioner.Dockerfile

@@ -2,7 +2,8 @@
 
 # Base Go environment
 # -------------------
-FROM golang:1.20-alpine as base
+# pinned because of https://github.com/moby/moby/issues/45935
+FROM golang:1.20.5-alpine as base
 WORKDIR /porter
 
 RUN apk update && apk add --no-cache gcc musl-dev git protoc

+ 8 - 6
internal/analytics/track_events.go

@@ -48,10 +48,12 @@ const (
 	ClusterDestroyingSuccess SegmentEvent = "Cluster Destroying Success"
 
 	// porter apps
-	StackLaunchStart    SegmentEvent = "Stack Launch Started"
-	StackLaunchComplete SegmentEvent = "Stack Launch Complete"
-	StackLaunchSuccess  SegmentEvent = "Stack Launch Success"
-	StackLaunchFailure  SegmentEvent = "Stack Launch Failure"
-	StackDeletion       SegmentEvent = "Stack Deletion"
-	StackBuildFailure   SegmentEvent = "Stack Build Failure"
+	StackLaunchStart      SegmentEvent = "Stack Launch Started"
+	StackLaunchComplete   SegmentEvent = "Stack Launch Complete"
+	StackLaunchSuccess    SegmentEvent = "Stack Launch Success"
+	StackLaunchFailure    SegmentEvent = "Stack Launch Failure"
+	StackDeletion         SegmentEvent = "Stack Deletion"
+	StackBuildProgressing SegmentEvent = "Stack Build Progressing"
+	StackBuildFailure     SegmentEvent = "Stack Build Failure"
+	StackBuildSuccess     SegmentEvent = "Stack Build Success"
 )

+ 38 - 8
internal/analytics/tracks.go

@@ -855,21 +855,23 @@ func StackDeletionTrack(opts *StackDeletionOpts) segmentTrack {
 	)
 }
 
-// StackBuildFailureOpts are the options for creating a track when a stack fails to build
-type StackBuildFailureOpts struct {
+// StackBuildOpts are the options for creating a track when a stack builds
+type StackBuildOpts struct {
 	*ProjectScopedTrackOpts
 
-	StackName   string
-	Email       string
-	FirstName   string
-	LastName    string
-	CompanyName string
+	StackName    string
+	ErrorMessage string
+	Email        string
+	FirstName    string
+	LastName     string
+	CompanyName  string
 }
 
 // StackBuildFailureTrack returns a track for when a stack fails to build
-func StackBuildFailureTrack(opts *StackBuildFailureOpts) segmentTrack {
+func StackBuildFailureTrack(opts *StackBuildOpts) segmentTrack {
 	additionalProps := make(map[string]interface{})
 	additionalProps["stack_name"] = opts.StackName
+	additionalProps["error_message"] = opts.ErrorMessage
 	additionalProps["email"] = opts.Email
 	additionalProps["name"] = opts.FirstName + " " + opts.LastName
 	additionalProps["company"] = opts.CompanyName
@@ -879,3 +881,31 @@ func StackBuildFailureTrack(opts *StackBuildFailureOpts) segmentTrack {
 		getDefaultSegmentTrack(additionalProps, StackBuildFailure),
 	)
 }
+
+// StackBuildSuccessTrack returns a track for when a stack succeeds to build
+func StackBuildSuccessTrack(opts *StackBuildOpts) segmentTrack {
+	additionalProps := make(map[string]interface{})
+	additionalProps["stack_name"] = opts.StackName
+	additionalProps["email"] = opts.Email
+	additionalProps["name"] = opts.FirstName + " " + opts.LastName
+	additionalProps["company"] = opts.CompanyName
+
+	return getSegmentProjectTrack(
+		opts.ProjectScopedTrackOpts,
+		getDefaultSegmentTrack(additionalProps, StackBuildSuccess),
+	)
+}
+
+// StackBuildProgressingTrack returns a track for when a stack starts to build
+func StackBuildProgressingTrack(opts *StackBuildOpts) segmentTrack {
+	additionalProps := make(map[string]interface{})
+	additionalProps["stack_name"] = opts.StackName
+	additionalProps["email"] = opts.Email
+	additionalProps["name"] = opts.FirstName + " " + opts.LastName
+	additionalProps["company"] = opts.CompanyName
+
+	return getSegmentProjectTrack(
+		opts.ProjectScopedTrackOpts,
+		getDefaultSegmentTrack(additionalProps, StackBuildProgressing),
+	)
+}

+ 2 - 1
services/cli_install_script_container/Dockerfile

@@ -1,4 +1,5 @@
-FROM golang:1.20-alpine
+# pinned because of https://github.com/moby/moby/issues/45935
+FROM golang:1.20.5-alpine
 
 WORKDIR /app
 COPY . .

+ 2 - 1
services/deploy_init_container/Dockerfile

@@ -1,4 +1,5 @@
-FROM golang:1.20-alpine as builder
+# pinned because of https://github.com/moby/moby/issues/45935
+FROM golang:1.20.5-alpine as builder
 
 WORKDIR /init-backend
 COPY main.go .

+ 2 - 1
services/migrator/Dockerfile

@@ -2,7 +2,8 @@
 
 # Base Go environment
 # -------------------
-FROM golang:1.20-alpine as base
+# pinned because of https://github.com/moby/moby/issues/45935
+FROM golang:1.20.5-alpine as base
 WORKDIR /porter
 
 RUN apk update && apk add --no-cache gcc musl-dev git

+ 2 - 1
services/porter_cli_container/dev.Dockerfile

@@ -2,7 +2,8 @@
 
 # Base Go environment
 # -------------------
-FROM golang:1.20 as base
+# pinned because of https://github.com/moby/moby/issues/45935
+FROM golang:1.20.5 as base
 WORKDIR /porter
 
 RUN apt-get update && apt-get install -y gcc musl-dev git

+ 2 - 1
services/preview_env_setup_job/Dockerfile

@@ -1,5 +1,6 @@
 # Build
-FROM golang:1.20-alpine as base
+# pinned because of https://github.com/moby/moby/issues/45935
+FROM golang:1.20.5-alpine as base
 
 WORKDIR /app
 COPY . .

+ 2 - 1
workers/Dockerfile

@@ -2,7 +2,8 @@
 
 # Buildtime environment
 # -------------------------------------------
-FROM golang:1.20-alpine as build
+# pinned because of https://github.com/moby/moby/issues/45935
+FROM golang:1.20.5-alpine as build
 WORKDIR /app
 
 RUN apk update && apk add gcc binutils-gold musl-dev