Explorar el Código

Merge pull request #1930 from porter-dev/nafees/hotfixes

[hotfix] Performance upgrades and more metadata for deployments
abelanger5 hace 4 años
padre
commit
fcddad2afe

+ 17 - 6
api/server/handlers/environment/list_deployments_by_cluster.go

@@ -59,10 +59,15 @@ func (c *ListDeploymentsByClusterHandler) ServeHTTP(w http.ResponseWriter, r *ht
 
 			env, err := c.Repo().Environment().ReadEnvironmentByID(project.ID, cluster.ID, deployment.EnvironmentID)
 
-			if err == nil {
-				updateDeploymentWithGithubWorkflowRunStatus(r.Context(), c.Config(), env, deployment)
+			if err != nil {
+				c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+				return
 			}
 
+			updateDeploymentWithGithubWorkflowRunStatus(r.Context(), c.Config(), env, deployment)
+
+			deployment.InstallationID = env.GitInstallationID
+
 			deployments = append(deployments, deployment)
 		}
 
@@ -108,6 +113,8 @@ func (c *ListDeploymentsByClusterHandler) ServeHTTP(w http.ResponseWriter, r *ht
 
 			updateDeploymentWithGithubWorkflowRunStatus(r.Context(), c.Config(), env, deployment)
 
+			deployment.InstallationID = env.GitInstallationID
+
 			deployments = append(deployments, deployment)
 		}
 
@@ -140,6 +147,10 @@ func updateDeploymentWithGithubWorkflowRunStatus(
 			ctx, deployment.RepoOwner, deployment.RepoName,
 			fmt.Sprintf("porter_%s_env.yml", env.Name), &github.ListWorkflowRunsOptions{
 				Branch: deployment.PRBranchFrom,
+				ListOptions: github.ListOptions{
+					Page:    1,
+					PerPage: 1,
+				},
 			},
 		)
 
@@ -148,12 +159,12 @@ func updateDeploymentWithGithubWorkflowRunStatus(
 
 			deployment.LastWorkflowRunURL = latestWorkflowRun.GetHTMLURL()
 
-			if deployment.Status != types.DeploymentStatusCreating &&
-				(latestWorkflowRun.GetStatus() == "in_progress" ||
-					latestWorkflowRun.GetStatus() == "queued") {
+			if (latestWorkflowRun.GetStatus() == "in_progress" ||
+				latestWorkflowRun.GetStatus() == "queued") &&
+				deployment.Status != types.DeploymentStatusCreating {
 				deployment.Status = types.DeploymentStatusUpdating
 			} else if latestWorkflowRun.GetStatus() == "completed" {
-				if latestWorkflowRun.GetConclusion() == "failed" {
+				if latestWorkflowRun.GetConclusion() == "failure" {
 					deployment.Status = types.DeploymentStatusFailed
 				} else if latestWorkflowRun.GetConclusion() == "timed_out" {
 					deployment.Status = types.DeploymentStatusTimedOut

+ 131 - 0
api/server/handlers/environment/trigger_deployment_workflow.go

@@ -0,0 +1,131 @@
+package environment
+
+import (
+	"context"
+	"errors"
+	"fmt"
+	"net/http"
+	"strconv"
+
+	"github.com/google/go-github/v41/github"
+	"github.com/porter-dev/porter/api/server/handlers"
+	"github.com/porter-dev/porter/api/server/shared"
+	"github.com/porter-dev/porter/api/server/shared/apierrors"
+	"github.com/porter-dev/porter/api/server/shared/config"
+	"github.com/porter-dev/porter/api/server/shared/requestutils"
+	"github.com/porter-dev/porter/api/types"
+	"github.com/porter-dev/porter/internal/models"
+)
+
+var ErrNoWorkflowRuns = errors.New("no previous workflow runs found")
+
+type TriggerDeploymentWorkflowHandler struct {
+	handlers.PorterHandlerReadWriter
+}
+
+func NewTriggerDeploymentWorkflowHandler(
+	config *config.Config,
+	decoderValidator shared.RequestDecoderValidator,
+	writer shared.ResultWriter,
+) *TriggerDeploymentWorkflowHandler {
+	return &TriggerDeploymentWorkflowHandler{
+		PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, decoderValidator, writer),
+	}
+}
+
+func (c *TriggerDeploymentWorkflowHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	project, _ := r.Context().Value(types.ProjectScope).(*models.Project)
+	cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
+
+	deplID, reqErr := requestutils.GetURLParamUint(r, "deployment_id")
+
+	if reqErr != nil {
+		c.HandleAPIError(w, r, reqErr)
+		return
+	}
+
+	depl, err := c.Repo().Environment().ReadDeploymentByID(project.ID, cluster.ID, deplID)
+
+	if err != nil {
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
+	}
+
+	if depl.Status == types.DeploymentStatusInactive {
+		return
+	}
+
+	env, err := c.Repo().Environment().ReadEnvironmentByID(project.ID, cluster.ID, depl.EnvironmentID)
+
+	if err != nil {
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
+	}
+
+	client, err := getGithubClientFromEnvironment(c.Config(), env)
+
+	if err != nil {
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
+	}
+
+	latestWorkflowRun, err := getLatestWorkflowRun(client, env.GitRepoOwner, env.GitRepoName,
+		fmt.Sprintf("porter_%s_env.yml", env.Name))
+
+	if err != nil && errors.Is(err, ErrNoWorkflowRuns) {
+		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, 400))
+		return
+	} else if err != nil {
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
+	}
+
+	if latestWorkflowRun.GetStatus() == "in_progress" || latestWorkflowRun.GetStatus() == "queued" {
+		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(fmt.Errorf("workflow already in progress"), 409))
+		return
+	}
+
+	ghResp, err := client.Actions.CreateWorkflowDispatchEventByFileName(
+		r.Context(), env.GitRepoOwner, env.GitRepoName, fmt.Sprintf("porter_%s_env.yml", env.Name),
+		github.CreateWorkflowDispatchEventRequest{
+			Ref: depl.PRBranchFrom,
+			Inputs: map[string]interface{}{
+				"pr_number":      strconv.FormatUint(uint64(depl.PullRequestID), 10),
+				"pr_title":       depl.PRName,
+				"pr_branch_from": depl.PRBranchFrom,
+				"pr_branch_into": depl.PRBranchInto,
+			},
+		},
+	)
+
+	if ghResp != nil && ghResp.StatusCode == 404 {
+		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(fmt.Errorf("workflow file not found"), 404))
+		return
+	}
+
+	if err != nil {
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
+	}
+}
+
+func getLatestWorkflowRun(client *github.Client, owner, repo, filename string) (*github.WorkflowRun, error) {
+	workflowRuns, _, err := client.Actions.ListWorkflowRunsByFileName(
+		context.Background(), owner, repo, filename, &github.ListWorkflowRunsOptions{
+			ListOptions: github.ListOptions{
+				Page:    1,
+				PerPage: 1,
+			},
+		},
+	)
+
+	if err != nil {
+		return nil, err
+	}
+
+	if workflowRuns.GetTotalCount() == 0 {
+		return nil, ErrNoWorkflowRuns
+	}
+
+	return workflowRuns.WorkflowRuns[0], nil
+}

+ 6 - 1
api/server/handlers/gitinstallation/rerun_workflow.go

@@ -85,7 +85,12 @@ func (c *RerunWorkflowHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 
 func getLatestWorkflowRun(client *github.Client, owner, repo, filename string) (*github.WorkflowRun, error) {
 	workflowRuns, _, err := client.Actions.ListWorkflowRunsByFileName(
-		context.Background(), owner, repo, filename, &github.ListWorkflowRunsOptions{},
+		context.Background(), owner, repo, filename, &github.ListWorkflowRunsOptions{
+			ListOptions: github.ListOptions{
+				Page:    1,
+				PerPage: 1,
+			},
+		},
 	)
 
 	if err != nil {

+ 29 - 0
api/server/router/cluster.go

@@ -405,6 +405,35 @@ func getClusterRoutes(
 			Router:   r,
 		})
 
+		// POST /api/projects/{project_id}/clusters/{cluster_id}/deployments/{deployment_id}/trigger_workflow -> environment.NewTriggerDeploymentWorkflowHandler
+		triggerDeploymentWorkflowEndpoint := factory.NewAPIEndpoint(
+			&types.APIRequestMetadata{
+				Verb:   types.APIVerbCreate,
+				Method: types.HTTPVerbPost,
+				Path: &types.Path{
+					Parent:       basePath,
+					RelativePath: relPath + "/deployments/{deployment_id}/trigger_workflow",
+				},
+				Scopes: []types.PermissionScope{
+					types.UserScope,
+					types.ProjectScope,
+					types.ClusterScope,
+				},
+			},
+		)
+
+		triggerDeploymentWorkflowHandler := environment.NewTriggerDeploymentWorkflowHandler(
+			config,
+			factory.GetDecoderValidator(),
+			factory.GetResultWriter(),
+		)
+
+		routes = append(routes, &Route{
+			Endpoint: triggerDeploymentWorkflowEndpoint,
+			Handler:  triggerDeploymentWorkflowHandler,
+			Router:   r,
+		})
+
 		// POST /api/projects/{project_id}/clusters/{cluster_id}/deployments/pull_request -> environment.NewEnablePullRequestHandler
 		enablePullRequestEndpoint := factory.NewAPIEndpoint(
 			&types.APIRequestMetadata{

+ 1 - 1
api/types/environment.go

@@ -48,12 +48,12 @@ type Deployment struct {
 	ID                 uint             `json:"id"`
 	CreatedAt          time.Time        `json:"created_at"`
 	UpdatedAt          time.Time        `json:"updated_at"`
-	GitInstallationID  uint             `json:"git_installation_id"`
 	EnvironmentID      uint             `json:"environment_id"`
 	Namespace          string           `json:"namespace"`
 	Status             DeploymentStatus `json:"status"`
 	Subdomain          string           `json:"subdomain"`
 	PullRequestID      uint             `json:"pull_request_id"`
+	InstallationID     uint             `json:"gh_installation_id"`
 	LastWorkflowRunURL string           `json:"last_workflow_run_url"`
 }
 

+ 0 - 39
internal/models/environment.go

@@ -79,42 +79,3 @@ func (d *Deployment) ToDeploymentType() *types.Deployment {
 		GitHubMetadata: ghMetadata,
 	}
 }
-
-type DeploymentWithEnvironment struct {
-	gorm.Model
-
-	Environment    *Environment
-	Namespace      string
-	Status         types.DeploymentStatus
-	Subdomain      string
-	PullRequestID  uint
-	GHDeploymentID int64
-	PRName         string
-	RepoName       string
-	RepoOwner      string
-	CommitSHA      string
-}
-
-func (d *DeploymentWithEnvironment) ToDeploymentType() *types.Deployment {
-
-	ghMetadata := &types.GitHubMetadata{
-		DeploymentID: d.GHDeploymentID,
-		PRName:       d.PRName,
-		RepoName:     d.RepoName,
-		RepoOwner:    d.RepoOwner,
-		CommitSHA:    d.CommitSHA,
-	}
-
-	return &types.Deployment{
-		CreatedAt:         d.CreatedAt,
-		UpdatedAt:         d.UpdatedAt,
-		ID:                d.Model.ID,
-		EnvironmentID:     d.Environment.ID,
-		GitInstallationID: d.Environment.GitInstallationID,
-		Namespace:         d.Namespace,
-		Status:            d.Status,
-		Subdomain:         d.Subdomain,
-		PullRequestID:     d.PullRequestID,
-		GitHubMetadata:    ghMetadata,
-	}
-}