Quellcode durchsuchen

Merge branch 'master' of github.com:porter-dev/porter into capi-preflight-checks-2

Feroze Mohideen vor 3 Jahren
Ursprung
Commit
8f260aa399
100 geänderte Dateien mit 882 neuen und 294 gelöschten Zeilen
  1. 1 0
      .github/workflows/production.yaml
  2. 13 8
      .github/workflows/test-backend.yml
  3. 2 0
      Tiltfile
  4. 1 11
      api/client/api.go
  5. 0 1
      api/client/base.go
  6. 16 36
      api/client/environment.go
  7. 0 2
      api/client/k8s.go
  8. 0 1
      api/client/user.go
  9. 5 7
      api/server/authn/handler.go
  10. 0 9
      api/server/authn/handler_test.go
  11. 0 2
      api/server/authn/session_helpers.go
  12. 5 7
      api/server/authz/cluster.go
  13. 0 3
      api/server/authz/git_installation.go
  14. 0 1
      api/server/authz/gitlab_integration.go
  15. 0 1
      api/server/authz/helm_repo.go
  16. 0 1
      api/server/authz/infra.go
  17. 0 1
      api/server/authz/invite.go
  18. 0 1
      api/server/authz/operation.go
  19. 0 2
      api/server/authz/policy/loader.go
  20. 0 5
      api/server/authz/policy_test.go
  21. 0 1
      api/server/authz/project.go
  22. 0 2
      api/server/authz/project_test.go
  23. 0 1
      api/server/authz/registry.go
  24. 0 2
      api/server/authz/release.go
  25. 0 1
      api/server/authz/stack.go
  26. 5 1
      api/server/handlers/api_contract/update.go
  27. 0 5
      api/server/handlers/api_token/create.go
  28. 0 1
      api/server/handlers/api_token/get.go
  29. 0 1
      api/server/handlers/api_token/list.go
  30. 0 1
      api/server/handlers/api_token/revoke.go
  31. 0 1
      api/server/handlers/billing/redirect_billing.go
  32. 0 3
      api/server/handlers/cluster/agent_status.go
  33. 0 6
      api/server/handlers/cluster/create.go
  34. 0 2
      api/server/handlers/cluster/create_candidate.go
  35. 0 2
      api/server/handlers/cluster/create_namespace.go
  36. 1 2
      api/server/handlers/cluster/delete.go
  37. 0 1
      api/server/handlers/cluster/delete_namespace.go
  38. 0 1
      api/server/handlers/cluster/detect_agent_installed.go
  39. 0 1
      api/server/handlers/cluster/detect_prometheus_installed.go
  40. 0 1
      api/server/handlers/cluster/get.go
  41. 0 3
      api/server/handlers/cluster/get_incident.go
  42. 0 3
      api/server/handlers/cluster/get_k8s_events.go
  43. 0 2
      api/server/handlers/cluster/get_kubeconfig.go
  44. 0 3
      api/server/handlers/cluster/get_logs.go
  45. 0 3
      api/server/handlers/cluster/get_logs_pod_values.go
  46. 0 3
      api/server/handlers/cluster/get_logs_revision_values.go
  47. 0 2
      api/server/handlers/cluster/get_namespace.go
  48. 0 1
      api/server/handlers/cluster/get_node.go
  49. 0 2
      api/server/handlers/cluster/get_pod_metrics.go
  50. 0 2
      api/server/handlers/cluster/get_pods.go
  51. 0 3
      api/server/handlers/cluster/get_porter_events.go
  52. 0 3
      api/server/handlers/cluster/get_porter_job_events.go
  53. 0 6
      api/server/handlers/cluster/install_agent.go
  54. 0 1
      api/server/handlers/cluster/list.go
  55. 0 1
      api/server/handlers/cluster/list_candidates.go
  56. 0 3
      api/server/handlers/cluster/list_incident_events.go
  57. 0 3
      api/server/handlers/cluster/list_incidents.go
  58. 0 2
      api/server/handlers/cluster/list_namespaces.go
  59. 0 2
      api/server/handlers/cluster/list_nginx_ingresses.go
  60. 0 1
      api/server/handlers/cluster/list_nodes.go
  61. 0 4
      api/server/handlers/cluster/notify_new_incident.go
  62. 0 3
      api/server/handlers/cluster/notify_resolved_incident.go
  63. 2 1
      api/server/handlers/cluster/rename.go
  64. 0 2
      api/server/handlers/cluster/resolve_candidate.go
  65. 0 1
      api/server/handlers/cluster/stream_helm_release.go
  66. 0 1
      api/server/handlers/cluster/stream_status.go
  67. 0 2
      api/server/handlers/cluster/update.go
  68. 0 3
      api/server/handlers/cluster/upgrade_agent.go
  69. 0 2
      api/server/handlers/cluster_integration/aws/get_cluster_info.go
  70. 0 1
      api/server/handlers/database/list.go
  71. 0 1
      api/server/handlers/database/update.go
  72. 0 3
      api/server/handlers/environment/common.go
  73. 0 6
      api/server/handlers/environment/create.go
  74. 0 6
      api/server/handlers/environment/create_deployment.go
  75. 127 0
      api/server/handlers/environment/create_deployment_by_cluster.go
  76. 0 4
      api/server/handlers/environment/delete.go
  77. 0 5
      api/server/handlers/environment/delete_deployment.go
  78. 0 4
      api/server/handlers/environment/enable_pull_request.go
  79. 0 8
      api/server/handlers/environment/finalize_deployment.go
  80. 186 0
      api/server/handlers/environment/finalize_deployment_by_cluster.go
  81. 0 4
      api/server/handlers/environment/finalize_deployment_with_errors.go
  82. 205 0
      api/server/handlers/environment/finalize_deployment_with_errors_by_cluster.go
  83. 0 1
      api/server/handlers/environment/get_deployment_by_env.go
  84. 0 1
      api/server/handlers/environment/get_environment.go
  85. 0 2
      api/server/handlers/environment/list.go
  86. 0 1
      api/server/handlers/environment/list_deployments.go
  87. 0 11
      api/server/handlers/environment/list_deployments_by_cluster.go
  88. 0 4
      api/server/handlers/environment/reenable_deployment.go
  89. 0 1
      api/server/handlers/environment/toggle_new_comment.go
  90. 0 4
      api/server/handlers/environment/trigger_deployment_workflow.go
  91. 0 4
      api/server/handlers/environment/update_deployment.go
  92. 154 0
      api/server/handlers/environment/update_deployment_by_cluster.go
  93. 0 3
      api/server/handlers/environment/update_deployment_status.go
  94. 157 0
      api/server/handlers/environment/update_deployment_status_by_cluster.go
  95. 0 4
      api/server/handlers/environment/update_environment_settings.go
  96. 0 4
      api/server/handlers/environment/validate_porter_yaml.go
  97. 2 4
      api/server/handlers/gitinstallation/get_accounts.go
  98. 0 2
      api/server/handlers/gitinstallation/get_buildpack.go
  99. 0 2
      api/server/handlers/gitinstallation/get_contents.go
  100. 0 1
      api/server/handlers/gitinstallation/get_permissions.go

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

@@ -41,6 +41,7 @@ jobs:
           COHERE_API_KEY=${{secrets.COHERE_KEY}}
           INTERCOM_APP_ID=${{secrets.INTERCOM_APP_ID}}
           INTERCOM_SRC=${{secrets.INTERCOM_SRC}}
+          HOTJAR_ID=${{secrets.HOTJAR_ID}}
           SEGMENT_WRITE_KEY=${{secrets.SEGMENT_WRITE_KEY}}
           SEGMENT_PUBLIC_KEY=${{secrets.SEGMENT_PUBLIC_KEY}}
           APPLICATION_CHART_REPO_URL=https://charts.getporter.dev

+ 13 - 8
.github/workflows/test-backend.yml

@@ -2,12 +2,17 @@ name: Backend CI
 on:
   - pull_request
 jobs:
-  backend-tests:
-    name: Run Go tests
-    runs-on: ubuntu-latest
+  testing_matrix:
+    strategy:
+      matrix:
+        go-version: [1.20.x]
+        os: [ubuntu-latest]
+        folder: [cli, api, cmd, internal, provisioner]
+    name: Running ${{ matrix.folder }} tests on Go ${{ matrix.go-version }} on ${{ matrix.os }}
+    runs-on: ${{ matrix.os }}
     steps:
-    - uses: actions/checkout@v3
-    - uses: actions/setup-go@v3
-      with:
-        go-version: '^1.15.1'
-    - run: go test ./...
+      - uses: actions/checkout@v3
+      - uses: actions/setup-go@v4
+        with:
+          go-version: ${{ matrix.go-version }}
+      - run: go test ./${{ matrix.folder }}/...

+ 2 - 0
Tiltfile

@@ -62,6 +62,8 @@ local_resource(
     resource_deps=["postgresql"],
     labels=["porter"]
 )
+# local_resource('public-url', serve_cmd='lt --subdomain "$(whoami)" --port 8080', resource_deps=["porter-dashboard"], labels=["porter"])
+# local_resource('public-url', serve_cmd='ngrok http 8081 --log=stdout', resource_deps=["porter-dashboard"], labels=["porter"])
 
 # Migrations
 local_resource(

+ 1 - 11
api/client/api.go

@@ -127,7 +127,6 @@ func (c *Client) postRequest(relPath string, data interface{}, response interfac
 
 	for i := 0; i < int(retryCount); i++ {
 		strData, err := json.Marshal(data)
-
 		if err != nil {
 			return nil
 		}
@@ -137,7 +136,6 @@ func (c *Client) postRequest(relPath string, data interface{}, response interfac
 			fmt.Sprintf("%s%s", c.BaseURL, relPath),
 			strings.NewReader(string(strData)),
 		)
-
 		if err != nil {
 			return err
 		}
@@ -182,7 +180,6 @@ func (c *Client) patchRequest(relPath string, data interface{}, response interfa
 
 	for i := 0; i < int(retryCount); i++ {
 		strData, err := json.Marshal(data)
-
 		if err != nil {
 			return nil
 		}
@@ -192,7 +189,6 @@ func (c *Client) patchRequest(relPath string, data interface{}, response interfa
 			fmt.Sprintf("%s%s", c.BaseURL, relPath),
 			strings.NewReader(string(strData)),
 		)
-
 		if err != nil {
 			return err
 		}
@@ -221,7 +217,6 @@ func (c *Client) patchRequest(relPath string, data interface{}, response interfa
 
 func (c *Client) deleteRequest(relPath string, data interface{}, response interface{}) error {
 	strData, err := json.Marshal(data)
-
 	if err != nil {
 		return nil
 	}
@@ -231,7 +226,6 @@ func (c *Client) deleteRequest(relPath string, data interface{}, response interf
 		fmt.Sprintf("%s%s", c.BaseURL, relPath),
 		strings.NewReader(string(strData)),
 	)
-
 	if err != nil {
 		return err
 	}
@@ -263,7 +257,6 @@ func (c *Client) sendRequest(req *http.Request, v interface{}, useCookie bool) (
 	}
 
 	res, err := c.HTTPClient.Do(req)
-
 	if err != nil {
 		return nil, err
 	}
@@ -302,18 +295,16 @@ func (c *Client) saveCookie(cookie *http.Cookie) error {
 	data, err := json.Marshal(&CookieStorage{
 		Cookie: cookie,
 	})
-
 	if err != nil {
 		return err
 	}
 
-	return ioutil.WriteFile(c.CookieFilePath, data, 0644)
+	return ioutil.WriteFile(c.CookieFilePath, data, 0o644)
 }
 
 // retrieves single cookie from file
 func (c *Client) getCookie() (*http.Cookie, error) {
 	data, err := ioutil.ReadFile(c.CookieFilePath)
-
 	if err != nil {
 		return nil, err
 	}
@@ -353,7 +344,6 @@ func GetProjectIDFromToken(token string) (uint, bool, error) {
 	}
 
 	decodedBytes, err := base64.RawStdEncoding.DecodeString(encoded)
-
 	if err != nil {
 		return 0, false, fmt.Errorf("could not decode jwt token from base64: %v", err)
 	}

+ 0 - 1
api/client/base.go

@@ -14,7 +14,6 @@ func (c *Client) GetPorterInstanceMetadata(ctx context.Context) (*sharedConfig.M
 		nil,
 		resp,
 	)
-
 	if err != nil {
 		return nil, err
 	}

+ 16 - 36
api/client/environment.go

@@ -24,17 +24,13 @@ func (c *Client) ListEnvironments(
 
 func (c *Client) CreateDeployment(
 	ctx context.Context,
-	projID, gitInstallationID, clusterID uint,
-	gitRepoOwner, gitRepoName string,
+	projID, clusterID uint,
 	req *types.CreateDeploymentRequest,
 ) (*types.Deployment, error) {
 	resp := &types.Deployment{}
 
 	err := c.postRequest(
-		fmt.Sprintf(
-			"/projects/%d/gitrepos/%d/%s/%s/clusters/%d/deployment",
-			projID, gitInstallationID, gitRepoOwner, gitRepoName, clusterID,
-		),
+		fmt.Sprintf("/projects/%d/clusters/%d/deployments", projID, clusterID),
 		req,
 		resp,
 	)
@@ -60,17 +56,13 @@ func (c *Client) GetDeployment(
 
 func (c *Client) UpdateDeployment(
 	ctx context.Context,
-	projID, gitInstallationID, clusterID uint,
-	gitRepoOwner, gitRepoName string,
-	req *types.UpdateDeploymentRequest,
+	projID, clusterID uint,
+	req *types.UpdateDeploymentByClusterRequest,
 ) (*types.Deployment, error) {
 	resp := &types.Deployment{}
 
-	err := c.postRequest(
-		fmt.Sprintf(
-			"/projects/%d/gitrepos/%d/%s/%s/clusters/%d/deployment/update",
-			projID, gitInstallationID, gitRepoOwner, gitRepoName, clusterID,
-		),
+	err := c.patchRequest(
+		fmt.Sprintf("/projects/%d/clusters/%d/deployments", projID, clusterID),
 		req,
 		resp,
 	)
@@ -80,17 +72,13 @@ func (c *Client) UpdateDeployment(
 
 func (c *Client) UpdateDeploymentStatus(
 	ctx context.Context,
-	projID, gitInstallationID, clusterID uint,
-	gitRepoOwner, gitRepoName string,
-	req *types.UpdateDeploymentStatusRequest,
+	projID, clusterID uint,
+	req *types.UpdateDeploymentStatusByClusterRequest,
 ) (*types.Deployment, error) {
 	resp := &types.Deployment{}
 
-	err := c.postRequest(
-		fmt.Sprintf(
-			"/projects/%d/gitrepos/%d/%s/%s/clusters/%d/deployment/update/status",
-			projID, gitInstallationID, gitRepoOwner, gitRepoName, clusterID,
-		),
+	err := c.patchRequest(
+		fmt.Sprintf("/projects/%d/clusters/%d/deployments/status", projID, clusterID),
 		req,
 		resp,
 	)
@@ -100,17 +88,13 @@ func (c *Client) UpdateDeploymentStatus(
 
 func (c *Client) FinalizeDeployment(
 	ctx context.Context,
-	projID, gitInstallationID, clusterID uint,
-	gitRepoOwner, gitRepoName string,
-	req *types.FinalizeDeploymentRequest,
+	projID, clusterID uint,
+	req *types.FinalizeDeploymentByClusterRequest,
 ) (*types.Deployment, error) {
 	resp := &types.Deployment{}
 
 	err := c.postRequest(
-		fmt.Sprintf(
-			"/projects/%d/gitrepos/%d/%s/%s/clusters/%d/deployment/finalize",
-			projID, gitInstallationID, gitRepoOwner, gitRepoName, clusterID,
-		),
+		fmt.Sprintf("/projects/%d/clusters/%d/deployments/finalize", projID, clusterID),
 		req,
 		resp,
 	)
@@ -120,17 +104,13 @@ func (c *Client) FinalizeDeployment(
 
 func (c *Client) FinalizeDeploymentWithErrors(
 	ctx context.Context,
-	projID, gitInstallationID, clusterID uint,
-	gitRepoOwner, gitRepoName string,
-	req *types.FinalizeDeploymentWithErrorsRequest,
+	projID, clusterID uint,
+	req *types.FinalizeDeploymentWithErrorsByClusterRequest,
 ) (*types.Deployment, error) {
 	resp := &types.Deployment{}
 
 	err := c.postRequest(
-		fmt.Sprintf(
-			"/projects/%d/gitrepos/%d/%s/%s/clusters/%d/deployment/finalize_errors",
-			projID, gitInstallationID, gitRepoOwner, gitRepoName, clusterID,
-		),
+		fmt.Sprintf("/projects/%d/clusters/%d/deployments/finalize_errors", projID, clusterID),
 		req,
 		resp,
 	)

+ 0 - 2
api/client/k8s.go

@@ -71,13 +71,11 @@ func (c *Client) GetKubeconfig(
 
 		if _, err := os.Stat(localKubeconfigPath); !os.IsNotExist(err) {
 			file, err := os.Open(localKubeconfigPath)
-
 			if err != nil {
 				return nil, err
 			}
 
 			data, err := io.ReadAll(file)
-
 			if err != nil {
 				return nil, err
 			}

+ 0 - 1
api/client/user.go

@@ -46,7 +46,6 @@ func (c *Client) Logout(ctx context.Context) error {
 		nil,
 		nil,
 	)
-
 	if err != nil {
 		return err
 	}

+ 5 - 7
api/server/authn/handler.go

@@ -70,7 +70,6 @@ func (authn *AuthN) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 
 	// if the bearer token is not found, look for a request cookie
 	session, err := authn.config.Store.Get(r, authn.config.ServerConf.CookieName)
-
 	if err != nil {
 		session.Values["authenticated"] = false
 
@@ -83,7 +82,7 @@ func (authn *AuthN) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	}
 
 	supportEmail := "support@porter.run"
-	cancelTime := time.Date(2023, 01, 31, 14, 30, 0, 0, time.Now().Local().Location())
+	cancelTime := time.Date(2023, 0o1, 31, 14, 30, 0, 0, time.Now().Local().Location())
 	if email, ok := session.Values["email"]; ok {
 		if email.(string) == supportEmail {
 			sess, _ := authn.config.Repo.Session().SelectSession(&models.Session{Key: session.ID})
@@ -139,7 +138,6 @@ func (authn *AuthN) verifyTokenWithNext(w http.ResponseWriter, r *http.Request,
 	// if the token has a stored token id and secret we check that the token is valid in the database
 	if tok.Secret != "" && tok.TokenID != "" {
 		apiToken, err := authn.config.Repo.APIToken().ReadAPIToken(tok.ProjectID, tok.TokenID)
-
 		if err != nil {
 			authn.sendForbiddenError(fmt.Errorf("token with id %s not valid", tok.TokenID), w, r)
 			return
@@ -179,7 +177,6 @@ func (authn *AuthN) nextWithAPIToken(w http.ResponseWriter, r *http.Request, tok
 func (authn *AuthN) nextWithUserID(w http.ResponseWriter, r *http.Request, userID uint) {
 	// search for the user
 	user, err := authn.config.Repo.User().ReadUser(userID)
-
 	if err != nil {
 		authn.sendForbiddenError(fmt.Errorf("user with id %d not found in database", userID), w, r)
 		return
@@ -201,8 +198,10 @@ func (authn *AuthN) sendForbiddenError(err error, w http.ResponseWriter, r *http
 	apierrors.HandleAPIError(authn.config.Logger, authn.config.Alerter, w, r, reqErr, true)
 }
 
-var errInvalidToken = fmt.Errorf("authorization header exists, but token is not valid")
-var errInvalidAuthHeader = fmt.Errorf("invalid authorization header in request")
+var (
+	errInvalidToken      = fmt.Errorf("authorization header exists, but token is not valid")
+	errInvalidAuthHeader = fmt.Errorf("invalid authorization header in request")
+)
 
 // getTokenFromRequest finds an `Authorization` header of the form `Bearer <token>`,
 // and returns a valid token if it exists.
@@ -217,7 +216,6 @@ func (authn *AuthN) getTokenFromRequest(r *http.Request) (*token.Token, error) {
 	reqToken = strings.TrimSpace(splitToken[1])
 
 	tok, err := token.GetTokenFromEncoded(reqToken, authn.config.TokenConf)
-
 	if err != nil {
 		return nil, errInvalidToken
 	}

+ 0 - 9
api/server/authn/handler_test.go

@@ -19,7 +19,6 @@ func TestAuthenticatedUserWithCookie(t *testing.T) {
 	config, handler, next := loadHandlers(t)
 
 	req, err := http.NewRequest("GET", "/auth-endpoint", nil)
-
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -40,7 +39,6 @@ func TestUnauthenticatedUserWithCookie(t *testing.T) {
 	_, handler, next := loadHandlers(t)
 
 	req, err := http.NewRequest("GET", "/auth-endpoint", nil)
-
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -57,7 +55,6 @@ func TestUnauthenticatedUserWithCookieRedirect(t *testing.T) {
 	_, handler, next := loadHandlersWithRedirect(t)
 
 	req, err := http.NewRequest("GET", "/auth-endpoint", nil)
-
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -69,7 +66,6 @@ func TestUnauthenticatedUserWithCookieRedirect(t *testing.T) {
 
 	assert.Equal(t, http.StatusFound, rr.Result().StatusCode)
 	gotLoc, err := rr.Result().Location()
-
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -82,7 +78,6 @@ func TestAuthenticatedUserWithToken(t *testing.T) {
 	config, handler, next := loadHandlers(t)
 
 	req, err := http.NewRequest("GET", "/auth-endpoint", nil)
-
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -103,7 +98,6 @@ func TestUnauthenticatedUserWithToken(t *testing.T) {
 	_, handler, next := loadHandlers(t)
 
 	req, err := http.NewRequest("GET", "/auth-endpoint", nil)
-
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -123,7 +117,6 @@ func TestAuthBadDatabaseRead(t *testing.T) {
 	config, handler, next := loadHandlers(t)
 
 	req, err := http.NewRequest("GET", "/auth-endpoint", nil)
-
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -150,7 +143,6 @@ func TestAuthBadSessionUserWrite(t *testing.T) {
 	config, handler, next := loadHandlers(t)
 
 	req, err := http.NewRequest("GET", "/auth-endpoint", nil)
-
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -178,7 +170,6 @@ func TestAuthBadSessionUserIDType(t *testing.T) {
 	config, handler, next := loadHandlers(t)
 
 	req, err := http.NewRequest("GET", "/auth-endpoint", nil)
-
 	if err != nil {
 		t.Fatal(err)
 	}

+ 0 - 2
api/server/authn/session_helpers.go

@@ -14,7 +14,6 @@ func SaveUserAuthenticated(
 	user *models.User,
 ) (string, error) {
 	session, err := config.Store.Get(r, config.ServerConf.CookieName)
-
 	if err != nil {
 		return "", err
 	}
@@ -41,7 +40,6 @@ func SaveUserUnauthenticated(
 	config *config.Config,
 ) error {
 	session, err := config.Store.Get(r, config.ServerConf.CookieName)
-
 	if err != nil {
 		return err
 	}

+ 5 - 7
api/server/authz/cluster.go

@@ -16,9 +16,11 @@ import (
 	"k8s.io/client-go/dynamic"
 )
 
-const KubernetesAgentCtxKey string = "k8s-agent"
-const KubernetesDynamicClientCtxKey string = "k8s-dyn-client"
-const HelmAgentCtxKey string = "helm-agent"
+const (
+	KubernetesAgentCtxKey         string = "k8s-agent"
+	KubernetesDynamicClientCtxKey string = "k8s-dyn-client"
+	HelmAgentCtxKey               string = "helm-agent"
+)
 
 type ClusterScopedFactory struct {
 	config *config.Config
@@ -47,7 +49,6 @@ func (p *ClusterScopedMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Reque
 	reqScopes, _ := r.Context().Value(types.RequestScopeCtxKey).(map[types.PermissionScope]*types.RequestAction)
 	clusterID := reqScopes[types.ClusterScope].Resource.UInt
 	cluster, err := p.config.Repo.Cluster().ReadCluster(proj.ID, clusterID)
-
 	if err != nil {
 		if err == gorm.ErrRecordNotFound {
 			apierrors.HandleAPIError(p.config.Logger, p.config.Alerter, w, r, apierrors.NewErrForbidden(
@@ -116,7 +117,6 @@ func (d *OutOfClusterAgentGetter) GetAgent(r *http.Request, cluster *models.Clus
 	}
 
 	agent, err := kubernetes.GetAgentOutOfClusterConfig(ooc)
-
 	if err != nil {
 		return nil, fmt.Errorf("failed to get agent: %s", err.Error())
 	}
@@ -140,7 +140,6 @@ func (d *OutOfClusterAgentGetter) GetHelmAgent(r *http.Request, cluster *models.
 
 	// if helm agent not found in context, construct it from k8s agent
 	k8sAgent, err := d.GetAgent(r, cluster, namespace)
-
 	if err != nil {
 		return nil, err
 	}
@@ -150,7 +149,6 @@ func (d *OutOfClusterAgentGetter) GetHelmAgent(r *http.Request, cluster *models.
 	}
 
 	helmAgent, err := helm.GetAgentFromK8sAgent("secret", namespace, d.config.Logger, k8sAgent)
-
 	if err != nil {
 		return nil, fmt.Errorf("failed to get Helm agent: %s", err.Error())
 	}

+ 0 - 3
api/server/authz/git_installation.go

@@ -75,7 +75,6 @@ func NewGitInstallationContext(ctx context.Context, ga *integrations.GithubAppIn
 // much overhead
 func (p *GitInstallationScopedMiddleware) doesUserHaveGitInstallationAccess(githubIntegrationID, gitInstallationID uint) error {
 	oauthInt, err := p.config.Repo.GithubAppOAuthIntegration().ReadGithubAppOauthIntegration(githubIntegrationID)
-
 	if err != nil {
 		return err
 	}
@@ -99,7 +98,6 @@ func (p *GitInstallationScopedMiddleware) doesUserHaveGitInstallationAccess(gith
 	accountIDs := make([]int64, 0)
 
 	AuthUser, _, err := client.Users.Get(context.Background(), "")
-
 	if err != nil {
 		return err
 	}
@@ -113,7 +111,6 @@ func (p *GitInstallationScopedMiddleware) doesUserHaveGitInstallationAccess(gith
 
 	for {
 		orgs, pages, err := client.Organizations.List(context.Background(), "", opts)
-
 		if err != nil {
 			return err
 		}

+ 0 - 1
api/server/authz/gitlab_integration.go

@@ -40,7 +40,6 @@ func (p *GitlabIntegrationScopedMiddleware) ServeHTTP(w http.ResponseWriter, r *
 	reqScopes, _ := r.Context().Value(types.RequestScopeCtxKey).(map[types.PermissionScope]*types.RequestAction)
 	integrationID := reqScopes[types.GitlabIntegrationScope].Resource.UInt
 	gi, err := p.config.Repo.GitlabIntegration().ReadGitlabIntegration(proj.ID, integrationID)
-
 	if err != nil {
 		if err == gorm.ErrRecordNotFound {
 			apierrors.HandleAPIError(p.config.Logger, p.config.Alerter, w, r, apierrors.NewErrForbidden(

+ 0 - 1
api/server/authz/helm_repo.go

@@ -40,7 +40,6 @@ func (p *HelmRepoScopedMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Requ
 	helmRepoID := reqScopes[types.HelmRepoScope].Resource.UInt
 
 	helmRepo, err := p.config.Repo.HelmRepo().ReadHelmRepo(proj.ID, helmRepoID)
-
 	if err != nil {
 		if err == gorm.ErrRecordNotFound {
 			apierrors.HandleAPIError(p.config.Logger, p.config.Alerter, w, r, apierrors.NewErrForbidden(

+ 0 - 1
api/server/authz/infra.go

@@ -40,7 +40,6 @@ func (p *InfraScopedMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request
 	infraID := reqScopes[types.InfraScope].Resource.UInt
 
 	infra, err := p.config.Repo.Infra().ReadInfra(proj.ID, infraID)
-
 	if err != nil {
 		if err == gorm.ErrRecordNotFound {
 			apierrors.HandleAPIError(p.config.Logger, p.config.Alerter, w, r, apierrors.NewErrForbidden(

+ 0 - 1
api/server/authz/invite.go

@@ -40,7 +40,6 @@ func (p *InviteScopedMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Reques
 	inviteID := reqScopes[types.InviteScope].Resource.UInt
 
 	invite, err := p.config.Repo.Invite().ReadInvite(proj.ID, inviteID)
-
 	if err != nil {
 		if err == gorm.ErrRecordNotFound {
 			apierrors.HandleAPIError(p.config.Logger, p.config.Alerter, w, r, apierrors.NewErrForbidden(

+ 0 - 1
api/server/authz/operation.go

@@ -39,7 +39,6 @@ func (p *OperationScopedMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Req
 
 	// look for matching operation for the infra
 	operation, err := p.config.Repo.Infra().ReadOperation(infra.ID, operationID)
-
 	if err != nil {
 		if errors.Is(err, gorm.ErrRecordNotFound) {
 			apierrors.HandleAPIError(p.config.Logger, p.config.Alerter, w, r, apierrors.NewErrForbidden(err), true)

+ 0 - 2
api/server/authz/policy/loader.go

@@ -111,7 +111,6 @@ func GetAPIPolicyFromUID(policyRepo repository.PolicyRepository, projectID uint,
 	default:
 		// look up the policy and make sure it exists
 		policyModel, err := policyRepo.ReadPolicy(projectID, uid)
-
 		if err != nil {
 			if errors.Is(err, gorm.ErrRecordNotFound) {
 				return nil, apierrors.NewErrPassThroughToClient(
@@ -124,7 +123,6 @@ func GetAPIPolicyFromUID(policyRepo repository.PolicyRepository, projectID uint,
 		}
 
 		apiPolicy, err := policyModel.ToAPIPolicyType()
-
 		if err != nil {
 			return nil, apierrors.NewErrInternal(err)
 		}

+ 0 - 5
api/server/authz/policy_test.go

@@ -31,7 +31,6 @@ func TestPolicyMiddlewareSuccessfulProjectCluster(t *testing.T) {
 	_, _, err := project.CreateProjectWithUser(config.Repo.Project(), &models.Project{
 		Name: "test-project",
 	}, user)
-
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -79,7 +78,6 @@ func TestPolicyMiddlewareSuccessfulApplication(t *testing.T) {
 	_, _, err := project.CreateProjectWithUser(config.Repo.Project(), &models.Project{
 		Name: "test-project",
 	}, user)
-
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -144,7 +142,6 @@ func TestPolicyMiddlewareInvalidPermissions(t *testing.T) {
 	_, _, err := project.CreateProjectWithUser(config.Repo.Project(), &models.Project{
 		Name: "test-project",
 	}, user)
-
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -178,7 +175,6 @@ func TestPolicyMiddlewareFailInvalidLoader(t *testing.T) {
 	_, _, err := project.CreateProjectWithUser(config.Repo.Project(), &models.Project{
 		Name: "test-project",
 	}, user)
-
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -211,7 +207,6 @@ func TestPolicyMiddlewareFailBadParam(t *testing.T) {
 	_, _, err := project.CreateProjectWithUser(config.Repo.Project(), &models.Project{
 		Name: "test-project",
 	}, user)
-
 	if err != nil {
 		t.Fatal(err)
 	}

+ 0 - 1
api/server/authz/project.go

@@ -38,7 +38,6 @@ func (p *ProjectScopedMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Reque
 	projID := reqScopes[types.ProjectScope].Resource.UInt
 
 	project, err := p.config.Repo.Project().ReadProject(projID)
-
 	if err != nil {
 		if err == gorm.ErrRecordNotFound {
 			apierrors.HandleAPIError(p.config.Logger, p.config.Alerter, w, r, apierrors.NewErrForbidden(

+ 0 - 2
api/server/authz/project_test.go

@@ -21,7 +21,6 @@ func TestProjectMiddlewareSuccessful(t *testing.T) {
 	proj, _, err := project.CreateProjectWithUser(config.Repo.Project(), &models.Project{
 		Name: "test-project",
 	}, user)
-
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -49,7 +48,6 @@ func TestProjectMiddlewareFailedRead(t *testing.T) {
 	_, _, err := project.CreateProjectWithUser(config.Repo.Project(), &models.Project{
 		Name: "test-project",
 	}, user)
-
 	if err != nil {
 		t.Fatal(err)
 	}

+ 0 - 1
api/server/authz/registry.go

@@ -40,7 +40,6 @@ func (p *RegistryScopedMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Requ
 	registryID := reqScopes[types.RegistryScope].Resource.UInt
 
 	registry, err := p.config.Repo.Registry().ReadRegistry(proj.ID, registryID)
-
 	if err != nil {
 		if err == gorm.ErrRecordNotFound {
 			apierrors.HandleAPIError(p.config.Logger, p.config.Alerter, w, r, apierrors.NewErrForbidden(

+ 0 - 2
api/server/authz/release.go

@@ -38,7 +38,6 @@ func (p *ReleaseScopedMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Reque
 	cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
 
 	helmAgent, err := p.agentGetter.GetHelmAgent(r, cluster, "")
-
 	if err != nil {
 		apierrors.HandleAPIError(p.config.Logger, p.config.Alerter, w, r, apierrors.NewErrInternal(err), true)
 		return
@@ -52,7 +51,6 @@ func (p *ReleaseScopedMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Reque
 	version, _ := requestutils.GetURLParamUint(r, types.URLParamReleaseVersion)
 
 	release, err := helmAgent.GetRelease(name, int(version), false)
-
 	if err != nil {
 		// ugly casing since at the time of this commit Helm doesn't have an errors package.
 		// so we rely on the Helm error containing "not found"

+ 0 - 1
api/server/authz/stack.go

@@ -40,7 +40,6 @@ func (p *StackScopedMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request
 	stackID := reqScopes[types.StackScope].Resource.Name
 
 	stack, err := p.config.Repo.Stack().ReadStackByStringID(proj.ID, stackID)
-
 	if err != nil {
 		if err == gorm.ErrRecordNotFound {
 			apierrors.HandleAPIError(p.config.Logger, p.config.Alerter, w, r, apierrors.NewErrForbidden(

+ 5 - 1
api/server/handlers/api_contract/update.go

@@ -36,6 +36,7 @@ func NewAPIContractUpdateHandler(
 func (c *APIContractUpdateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	ctx := r.Context()
 	project, _ := ctx.Value(types.ProjectScope).(*models.Project)
+	user, _ := ctx.Value(types.UserScope).(*models.User)
 
 	var apiContract porterv1.Contract
 
@@ -46,7 +47,7 @@ func (c *APIContractUpdateHandler) ServeHTTP(w http.ResponseWriter, r *http.Requ
 		return
 	}
 
-	if !project.CapiProvisionerEnabled && c.Config().DisableCAPIProvisioner {
+	if !project.CapiProvisionerEnabled && !c.Config().EnableCAPIProvisioner {
 		// return dummy data if capi provisioner disabled in project settings, and as env var
 		// TODO: remove this stub when we can spin up all services locally, easily
 		clusterID := apiContract.Cluster.ClusterId
@@ -99,6 +100,9 @@ func (c *APIContractUpdateHandler) ServeHTTP(w http.ResponseWriter, r *http.Requ
 		return
 	}
 
+	apiContract.User = &porterv1.User{
+		Id: int32(user.ID),
+	}
 	updateRequest := connect.NewRequest(&porterv1.UpdateContractRequest{
 		Contract: &apiContract,
 	})

+ 0 - 5
api/server/handlers/api_token/create.go

@@ -59,14 +59,12 @@ func (p *APITokenCreateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request
 	}
 
 	uid, err := encryption.GenerateRandomBytes(16)
-
 	if err != nil {
 		p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
 	}
 
 	secretKey, err := encryption.GenerateRandomBytes(16)
-
 	if err != nil {
 		p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
@@ -74,7 +72,6 @@ func (p *APITokenCreateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request
 
 	// hash the secret key for storage in the db
 	hashedToken, err := bcrypt.GenerateFromPassword([]byte(secretKey), 8)
-
 	if err != nil {
 		p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
@@ -101,14 +98,12 @@ func (p *APITokenCreateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request
 
 	// generate porter jwt token
 	jwt, err := token.GetStoredTokenForAPI(user.ID, proj.ID, apiToken.UniqueID, secretKey)
-
 	if err != nil {
 		p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
 	}
 
 	encoded, err := jwt.EncodeToken(p.Config().TokenConf)
-
 	if err != nil {
 		p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return

+ 0 - 1
api/server/handlers/api_token/get.go

@@ -47,7 +47,6 @@ func (p *APITokenGetHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	}
 
 	token, err := p.Repo().APIToken().ReadAPIToken(proj.ID, tokenID)
-
 	if err != nil {
 		if errors.Is(err, gorm.ErrRecordNotFound) {
 			p.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(

+ 0 - 1
api/server/handlers/api_token/list.go

@@ -35,7 +35,6 @@ func (p *APITokenListHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 	}
 
 	tokens, err := p.Repo().APIToken().ListAPITokensByProjectID(proj.ID)
-
 	if err != nil {
 		p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return

+ 0 - 1
api/server/handlers/api_token/revoke.go

@@ -46,7 +46,6 @@ func (p *APITokenRevokeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request
 	}
 
 	token, err := p.Repo().APIToken().ReadAPIToken(proj.ID, tokenID)
-
 	if err != nil {
 		if errors.Is(err, gorm.ErrRecordNotFound) {
 			p.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(

+ 0 - 1
api/server/handlers/billing/redirect_billing.go

@@ -55,7 +55,6 @@ func (c *RedirectBillingHandler) ServeHTTP(w http.ResponseWriter, r *http.Reques
 	}
 
 	redirectURI, err := c.Config().BillingManager.GetRedirectURI(user, proj)
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return

+ 0 - 3
api/server/handlers/cluster/agent_status.go

@@ -32,7 +32,6 @@ func (c *GetAgentStatusHandler) ServeHTTP(w http.ResponseWriter, r *http.Request
 	cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
 
 	k8sAgent, err := c.GetAgent(r, cluster, "porter-agent-system")
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
@@ -40,14 +39,12 @@ func (c *GetAgentStatusHandler) ServeHTTP(w http.ResponseWriter, r *http.Request
 
 	// get agent service
 	agentSvc, err := porter_agent.GetAgentService(k8sAgent.Clientset)
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
 	}
 
 	status, err := porter_agent.GetAgentStatus(k8sAgent.Clientset, agentSvc)
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return

+ 0 - 6
api/server/handlers/cluster/create.go

@@ -41,7 +41,6 @@ func (c *CreateClusterManualHandler) ServeHTTP(w http.ResponseWriter, r *http.Re
 	}
 
 	cluster, err := getClusterModelFromManualRequest(c.Repo(), proj, request)
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
@@ -69,7 +68,6 @@ func getClusterModelFromManualRequest(
 
 		// check that the integration exists
 		_, err := repo.GCPIntegration().ReadGCPIntegration(project.ID, request.GCPIntegrationID)
-
 		if err != nil {
 			return nil, fmt.Errorf("gcp integration not found")
 		}
@@ -78,7 +76,6 @@ func getClusterModelFromManualRequest(
 
 		// check that the integration exists
 		_, err := repo.AWSIntegration().ReadAWSIntegration(project.ID, request.AWSIntegrationID)
-
 		if err != nil {
 			return nil, fmt.Errorf("aws integration not found")
 		}
@@ -95,7 +92,6 @@ func getClusterModelFromManualRequest(
 		// if it matches the base64 regex, decode it
 		if re.MatchString(request.CertificateAuthorityData) {
 			decoded, err := base64.StdEncoding.DecodeString(request.CertificateAuthorityData)
-
 			if err != nil {
 				return nil, err
 			}
@@ -124,7 +120,6 @@ func createClusterFromCandidate(
 ) (*models.Cluster, *models.ClusterCandidate, error) {
 	// we query the repo again to get the decrypted version of the cluster candidate
 	cc, err := repo.Cluster().ReadClusterCandidate(project.ID, candidate.ID)
-
 	if err != nil {
 		return nil, nil, err
 	}
@@ -143,7 +138,6 @@ func createClusterFromCandidate(
 	}
 
 	cluster, err := cResolver.ResolveCluster(repo)
-
 	if err != nil {
 		return nil, nil, err
 	}

+ 0 - 2
api/server/handlers/cluster/create_candidate.go

@@ -40,7 +40,6 @@ func (c *CreateClusterCandidateHandler) ServeHTTP(w http.ResponseWriter, r *http
 	}
 
 	ccs, err := getClusterCandidateModelsFromRequest(c.Repo(), proj, request, c.Config().ServerConf.IsLocal)
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
@@ -101,7 +100,6 @@ func getClusterCandidateModelsFromRequest(
 		// can only use "local" auth mechanism if the server is running locally
 		isServerLocal && request.IsLocal,
 	)
-
 	if err != nil {
 		return nil, err
 	}

+ 0 - 2
api/server/handlers/cluster/create_namespace.go

@@ -40,7 +40,6 @@ func (c *CreateNamespaceHandler) ServeHTTP(w http.ResponseWriter, r *http.Reques
 	cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
 
 	agent, err := c.GetAgent(r, cluster, "")
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
@@ -56,7 +55,6 @@ func (c *CreateNamespaceHandler) ServeHTTP(w http.ResponseWriter, r *http.Reques
 	}
 
 	namespace, err := agent.CreateNamespace(request.Name, request.Labels)
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return

+ 1 - 2
api/server/handlers/cluster/delete.go

@@ -35,7 +35,7 @@ func (c *ClusterDeleteHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 	cluster, _ := ctx.Value(types.ClusterScope).(*models.Cluster)
 
 	if cluster.ProvisionedBy == "CAPI" {
-		if !c.Config().DisableCAPIProvisioner {
+		if c.Config().EnableCAPIProvisioner {
 			revisions, err := c.Config().Repo.APIContractRevisioner().List(ctx, cluster.ProjectID, cluster.ID)
 			if err != nil {
 				e := fmt.Errorf("error listing revisions for cluster %d: %w", cluster.ID, err)
@@ -66,7 +66,6 @@ func (c *ClusterDeleteHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 	}
 
 	err := c.Repo().Cluster().DeleteCluster(cluster)
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return

+ 0 - 1
api/server/handlers/cluster/delete_namespace.go

@@ -39,7 +39,6 @@ func (c *DeleteNamespaceHandler) ServeHTTP(w http.ResponseWriter, r *http.Reques
 	}
 
 	agent, err := c.GetAgent(r, cluster, "")
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return

+ 0 - 1
api/server/handlers/cluster/detect_agent_installed.go

@@ -35,7 +35,6 @@ func (c *DetectAgentInstalledHandler) ServeHTTP(w http.ResponseWriter, r *http.R
 	cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
 
 	agent, err := c.GetAgent(r, cluster, "")
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return

+ 0 - 1
api/server/handlers/cluster/detect_prometheus_installed.go

@@ -30,7 +30,6 @@ func (c *DetectPrometheusInstalledHandler) ServeHTTP(w http.ResponseWriter, r *h
 	cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
 
 	agent, err := c.GetAgent(r, cluster, "")
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return

+ 0 - 1
api/server/handlers/cluster/get.go

@@ -37,7 +37,6 @@ func (c *ClusterGetHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	}
 
 	agent, err := c.GetAgent(r, cluster, "")
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return

+ 0 - 3
api/server/handlers/cluster/get_incident.go

@@ -41,7 +41,6 @@ func (c *GetIncidentHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	}
 
 	agent, err := c.GetAgent(r, cluster, "")
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
@@ -49,14 +48,12 @@ func (c *GetIncidentHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 
 	// get agent service
 	agentSvc, err := porter_agent.GetAgentService(agent.Clientset)
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
 	}
 
 	incident, err := porter_agent.GetIncidentByID(agent.Clientset, agentSvc, incidentID)
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return

+ 0 - 3
api/server/handlers/cluster/get_k8s_events.go

@@ -39,7 +39,6 @@ func (c *GetKubernetesEventsHandler) ServeHTTP(w http.ResponseWriter, r *http.Re
 	}
 
 	agent, err := c.GetAgent(r, cluster, "")
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
@@ -47,14 +46,12 @@ func (c *GetKubernetesEventsHandler) ServeHTTP(w http.ResponseWriter, r *http.Re
 
 	// get agent service
 	agentSvc, err := porter_agent.GetAgentService(agent.Clientset)
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
 	}
 
 	logs, err := porter_agent.GetHistoricalKubernetesEvents(agent.Clientset, agentSvc, request)
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return

+ 0 - 2
api/server/handlers/cluster/get_kubeconfig.go

@@ -70,14 +70,12 @@ func (c *GetTemporaryKubeconfigHandler) ServeHTTP(w http.ResponseWriter, r *http
 	}
 
 	kubeconfig, err := outOfClusterConfig.CreateRawConfigFromCluster()
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
 	}
 
 	kubeconfigBytes, err := clientcmd.Write(*kubeconfig)
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return

+ 0 - 3
api/server/handlers/cluster/get_logs.go

@@ -39,7 +39,6 @@ func (c *GetLogsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	}
 
 	agent, err := c.GetAgent(r, cluster, "")
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
@@ -47,14 +46,12 @@ func (c *GetLogsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 
 	// get agent service
 	agentSvc, err := porter_agent.GetAgentService(agent.Clientset)
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
 	}
 
 	logs, err := porter_agent.GetHistoricalLogs(agent.Clientset, agentSvc, request)
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return

+ 0 - 3
api/server/handlers/cluster/get_logs_pod_values.go

@@ -39,7 +39,6 @@ func (c *GetLogPodValuesHandler) ServeHTTP(w http.ResponseWriter, r *http.Reques
 	}
 
 	agent, err := c.GetAgent(r, cluster, "")
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
@@ -47,14 +46,12 @@ func (c *GetLogPodValuesHandler) ServeHTTP(w http.ResponseWriter, r *http.Reques
 
 	// get agent service
 	agentSvc, err := porter_agent.GetAgentService(agent.Clientset)
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
 	}
 
 	podVals, err := porter_agent.GetPodValues(agent.Clientset, agentSvc, request)
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return

+ 0 - 3
api/server/handlers/cluster/get_logs_revision_values.go

@@ -39,7 +39,6 @@ func (c *GetLogRevisionValuesHandler) ServeHTTP(w http.ResponseWriter, r *http.R
 	}
 
 	agent, err := c.GetAgent(r, cluster, "")
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
@@ -47,14 +46,12 @@ func (c *GetLogRevisionValuesHandler) ServeHTTP(w http.ResponseWriter, r *http.R
 
 	// get agent service
 	agentSvc, err := porter_agent.GetAgentService(agent.Clientset)
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
 	}
 
 	revisions, err := porter_agent.GetRevisionValues(agent.Clientset, agentSvc, request)
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return

+ 0 - 2
api/server/handlers/cluster/get_namespace.go

@@ -40,14 +40,12 @@ func (c *GetNamespaceHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 	cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
 
 	agent, err := c.GetAgent(r, cluster, "")
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
 	}
 
 	namespace, err := agent.GetNamespace(ns)
-
 	if err != nil {
 		if errors.IsNotFound(err) {
 			c.HandleAPIError(w, r, apierrors.NewErrNotFound(err))

+ 0 - 1
api/server/handlers/cluster/get_node.go

@@ -34,7 +34,6 @@ func (c *GetNodeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	name, _ := requestutils.GetURLParamString(r, types.URLParamNodeName)
 
 	agent, err := c.GetAgent(r, cluster, "")
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return

+ 0 - 2
api/server/handlers/cluster/get_pod_metrics.go

@@ -40,7 +40,6 @@ func (c *GetPodMetricsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 	cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
 
 	agent, err := c.GetAgent(r, cluster, "")
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
@@ -55,7 +54,6 @@ func (c *GetPodMetricsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 	}
 
 	rawQuery, err := prometheus.QueryPrometheus(agent.Clientset, promSvc, &request.QueryOpts)
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return

+ 0 - 2
api/server/handlers/cluster/get_pods.go

@@ -39,7 +39,6 @@ func (c *GetPodsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
 
 	agent, err := c.GetAgent(r, cluster, "")
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
@@ -48,7 +47,6 @@ func (c *GetPodsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	pods := []v1.Pod{}
 	for _, selector := range request.Selectors {
 		podsList, err := agent.GetPodsByLabel(selector, request.Namespace)
-
 		if err != nil {
 			c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 			return

+ 0 - 3
api/server/handlers/cluster/get_porter_events.go

@@ -40,7 +40,6 @@ func (c *GetPorterEventsHandler) ServeHTTP(w http.ResponseWriter, r *http.Reques
 	}
 
 	agent, err := c.GetAgent(r, cluster, "")
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
@@ -48,14 +47,12 @@ func (c *GetPorterEventsHandler) ServeHTTP(w http.ResponseWriter, r *http.Reques
 
 	// get agent service
 	agentSvc, err := porter_agent.GetAgentService(agent.Clientset)
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
 	}
 
 	events, err := porter_agent.ListPorterEvents(agent.Clientset, agentSvc, request)
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return

+ 0 - 3
api/server/handlers/cluster/get_porter_job_events.go

@@ -40,7 +40,6 @@ func (c *GetPorterJobEventsHandler) ServeHTTP(w http.ResponseWriter, r *http.Req
 	}
 
 	agent, err := c.GetAgent(r, cluster, "")
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
@@ -48,14 +47,12 @@ func (c *GetPorterJobEventsHandler) ServeHTTP(w http.ResponseWriter, r *http.Req
 
 	// get agent service
 	agentSvc, err := porter_agent.GetAgentService(agent.Clientset)
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
 	}
 
 	events, err := porter_agent.ListPorterJobEvents(agent.Clientset, agentSvc, request)
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return

+ 0 - 6
api/server/handlers/cluster/install_agent.go

@@ -47,14 +47,12 @@ func (c *InstallAgentHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 	cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
 
 	k8sAgent, err := c.GetAgent(r, cluster, "porter-agent-system")
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
 	}
 
 	helmAgent, err := c.GetHelmAgent(r, cluster, "porter-agent-system")
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
@@ -68,7 +66,6 @@ func (c *InstallAgentHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 	}
 
 	chart, err := loader.LoadChartPublic(c.Config().ServerConf.DefaultAddonHelmRepoURL, "porter-agent", "")
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
@@ -84,14 +81,12 @@ func (c *InstallAgentHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 
 	// add api token to values
 	jwt, err := token.GetTokenForAPI(user.ID, proj.ID)
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
 	}
 
 	encoded, err := jwt.EncodeToken(c.Config().TokenConf)
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
@@ -159,7 +154,6 @@ func (c *InstallAgentHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 
 func checkAndDeleteOlderAgent(k8sAgent *kubernetes.Agent, helmAgent *helm.Agent) error {
 	namespaceList, err := k8sAgent.Clientset.CoreV1().Namespaces().List(context.Background(), v1.ListOptions{})
-
 	if err != nil {
 		return fmt.Errorf("error listing namespaces: %w", err)
 	}

+ 0 - 1
api/server/handlers/cluster/list.go

@@ -30,7 +30,6 @@ func (p *ClusterListHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 
 	// read all clusters for this project
 	clusters, err := p.Repo().Cluster().ListClustersByProjectID(proj.ID)
-
 	if err != nil {
 		p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return

+ 0 - 1
api/server/handlers/cluster/list_candidates.go

@@ -28,7 +28,6 @@ func (c *ListClusterCandidatesHandler) ServeHTTP(w http.ResponseWriter, r *http.
 	proj, _ := r.Context().Value(types.ProjectScope).(*models.Project)
 
 	ccs, err := c.Repo().Cluster().ListClusterCandidatesByProjectID(proj.ID)
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return

+ 0 - 3
api/server/handlers/cluster/list_incident_events.go

@@ -39,7 +39,6 @@ func (c *ListIncidentEventsHandler) ServeHTTP(w http.ResponseWriter, r *http.Req
 	}
 
 	agent, err := c.GetAgent(r, cluster, "")
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
@@ -47,14 +46,12 @@ func (c *ListIncidentEventsHandler) ServeHTTP(w http.ResponseWriter, r *http.Req
 
 	// get agent service
 	agentSvc, err := porter_agent.GetAgentService(agent.Clientset)
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
 	}
 
 	events, err := porter_agent.ListIncidentEvents(agent.Clientset, agentSvc, request)
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return

+ 0 - 3
api/server/handlers/cluster/list_incidents.go

@@ -39,7 +39,6 @@ func (c *ListIncidentsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 	}
 
 	agent, err := c.GetAgent(r, cluster, "")
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
@@ -47,14 +46,12 @@ func (c *ListIncidentsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 
 	// get agent service
 	agentSvc, err := porter_agent.GetAgentService(agent.Clientset)
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
 	}
 
 	incidents, err := porter_agent.ListIncidents(agent.Clientset, agentSvc, request)
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return

+ 0 - 2
api/server/handlers/cluster/list_namespaces.go

@@ -32,14 +32,12 @@ func (c *ListNamespacesHandler) ServeHTTP(w http.ResponseWriter, r *http.Request
 	cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
 
 	agent, err := c.GetAgent(r, cluster, "")
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
 	}
 
 	namespaceList, err := agent.ListNamespaces()
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return

+ 0 - 2
api/server/handlers/cluster/list_nginx_ingresses.go

@@ -32,14 +32,12 @@ func (c *ListNGINXIngressesHandler) ServeHTTP(w http.ResponseWriter, r *http.Req
 	cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
 
 	agent, err := c.GetAgent(r, cluster, "")
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
 	}
 
 	ingresses, err := prometheus.GetIngressesWithNGINXAnnotation(agent.Clientset)
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return

+ 0 - 1
api/server/handlers/cluster/list_nodes.go

@@ -32,7 +32,6 @@ func (c *ListNodesHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
 
 	agent, err := c.GetAgent(r, cluster, "")
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return

+ 0 - 4
api/server/handlers/cluster/notify_new_incident.go

@@ -58,7 +58,6 @@ func (c *NotifyNewIncidentHandler) ServeHTTP(w http.ResponseWriter, r *http.Requ
 
 	if rel != nil && rel.NotificationConfig != 0 {
 		conf, err := c.Repo().NotificationConfig().ReadNotificationConfig(rel.NotificationConfig)
-
 		if err != nil {
 			c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 			return
@@ -68,7 +67,6 @@ func (c *NotifyNewIncidentHandler) ServeHTTP(w http.ResponseWriter, r *http.Requ
 	}
 
 	users, err := getUsersByProjectID(c.Repo(), cluster.ProjectID)
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
@@ -119,7 +117,6 @@ func (c *NotifyNewIncidentHandler) ServeHTTP(w http.ResponseWriter, r *http.Requ
 		}
 
 		err := multi.NotifyNew(request, url)
-
 		if err != nil {
 			c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 			return
@@ -129,7 +126,6 @@ func (c *NotifyNewIncidentHandler) ServeHTTP(w http.ResponseWriter, r *http.Requ
 
 func getUsersByProjectID(repo repository.Repository, projectID uint) ([]*models.User, error) {
 	roles, err := repo.Project().ListProjectRoles(projectID)
-
 	if err != nil {
 		return nil, err
 	}

+ 0 - 3
api/server/handlers/cluster/notify_resolved_incident.go

@@ -57,7 +57,6 @@ func (c *NotifyResolvedIncidentHandler) ServeHTTP(w http.ResponseWriter, r *http
 
 	if rel != nil && rel.NotificationConfig != 0 {
 		conf, err := c.Repo().NotificationConfig().ReadNotificationConfig(rel.NotificationConfig)
-
 		if err != nil {
 			c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 			return
@@ -74,7 +73,6 @@ func (c *NotifyResolvedIncidentHandler) ServeHTTP(w http.ResponseWriter, r *http
 
 	if sc := c.Config().ServerConf; sc.SendgridAPIKey != "" && sc.SendgridSenderEmail != "" && sc.SendgridIncidentAlertTemplateID != "" {
 		users, err := getUsersByProjectID(c.Repo(), cluster.ProjectID)
-
 		if err != nil {
 			c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 			return
@@ -118,7 +116,6 @@ func (c *NotifyResolvedIncidentHandler) ServeHTTP(w http.ResponseWriter, r *http
 		}
 
 		err := multi.NotifyResolved(request, url)
-
 		if err != nil {
 			c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 			return

+ 2 - 1
api/server/handlers/cluster/rename.go

@@ -1,6 +1,8 @@
 package cluster
 
 import (
+	"net/http"
+
 	"github.com/porter-dev/porter/api/server/authz"
 	"github.com/porter-dev/porter/api/server/handlers"
 	"github.com/porter-dev/porter/api/server/shared"
@@ -8,7 +10,6 @@ import (
 	"github.com/porter-dev/porter/api/server/shared/config"
 	"github.com/porter-dev/porter/api/types"
 	"github.com/porter-dev/porter/internal/models"
-	"net/http"
 )
 
 type RenameClusterHandler struct {

+ 0 - 2
api/server/handlers/cluster/resolve_candidate.go

@@ -40,14 +40,12 @@ func (c *ResolveClusterCandidateHandler) ServeHTTP(w http.ResponseWriter, r *htt
 	}
 
 	cc, err := c.Repo().Cluster().ReadClusterCandidate(proj.ID, ccID)
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
 	}
 
 	cluster, cc, err := createClusterFromCandidate(c.Repo(), proj, user, cc, request)
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return

+ 0 - 1
api/server/handlers/cluster/stream_helm_release.go

@@ -40,7 +40,6 @@ func (c *StreamHelmReleaseHandler) ServeHTTP(w http.ResponseWriter, r *http.Requ
 	cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
 
 	agent, err := c.GetAgent(r, cluster, "")
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return

+ 0 - 1
api/server/handlers/cluster/stream_status.go

@@ -41,7 +41,6 @@ func (c *StreamStatusHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 	cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
 
 	agent, err := c.GetAgent(r, cluster, "")
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return

+ 0 - 2
api/server/handlers/cluster/update.go

@@ -41,7 +41,6 @@ func (c *ClusterUpdateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 	// sure that the old cluster name is set
 	if cluster.AWSIntegrationID != 0 && request.AWSClusterID == "" {
 		awsInt, err := c.Repo().AWSIntegration().ReadAWSIntegration(cluster.ProjectID, cluster.AWSIntegrationID)
-
 		if err != nil {
 			c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 			return
@@ -74,7 +73,6 @@ func (c *ClusterUpdateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 	}
 
 	cluster, err := c.Repo().Cluster().UpdateCluster(cluster)
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return

+ 0 - 3
api/server/handlers/cluster/upgrade_agent.go

@@ -34,21 +34,18 @@ func NewUpgradeAgentHandler(
 func (c *UpgradeAgentHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
 	helmAgent, err := c.GetHelmAgent(r, cluster, "porter-agent-system")
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
 	}
 
 	currRelease, err := helmAgent.GetRelease("porter-agent", 0, false)
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
 	}
 
 	chart, err := loader.LoadChartPublic(c.Config().ServerConf.DefaultAddonHelmRepoURL, "porter-agent", "")
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return

+ 0 - 2
api/server/handlers/cluster_integration/aws/get_cluster_info.go

@@ -42,7 +42,6 @@ func (c *GetClusterInfoHandler) ServeHTTP(w http.ResponseWriter, r *http.Request
 	}
 
 	awsInt, err := c.Repo().AWSIntegration().ReadAWSIntegration(proj.ID, cluster.AWSIntegrationID)
-
 	if err != nil {
 		if errors.Is(err, gorm.ErrRecordNotFound) {
 			c.HandleAPIError(w, r, apierrors.NewErrNotFound(fmt.Errorf("no AWS integration found with project ID: %d and "+
@@ -56,7 +55,6 @@ func (c *GetClusterInfoHandler) ServeHTTP(w http.ResponseWriter, r *http.Request
 	}
 
 	awsSession, err := awsInt.GetSession()
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(fmt.Errorf("error fetching new session for AWS with "+
 			"project ID: %d and integration ID: %d. Error: %w", proj.ID, cluster.AWSIntegrationID, err), http.StatusConflict))

+ 0 - 1
api/server/handlers/database/list.go

@@ -31,7 +31,6 @@ func (p *DatabaseListHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 
 	// read all clusters for this project
 	dbs, err := p.Repo().Database().ListDatabases(proj.ID, cluster.ID)
-
 	if err != nil {
 		p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return

+ 0 - 1
api/server/handlers/database/update.go

@@ -37,7 +37,6 @@ func (p *DatabaseUpdateStatusHandler) ServeHTTP(w http.ResponseWriter, r *http.R
 
 	// read all clusters for this project
 	db, err := p.Repo().Database().ReadDatabaseByInfraID(proj.ID, infra.ID)
-
 	if err != nil {
 		p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return

+ 0 - 3
api/server/handlers/environment/common.go

@@ -26,7 +26,6 @@ var (
 func getGithubClientFromEnvironment(config *config.Config, env *models.Environment) (*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)
 	}
@@ -38,7 +37,6 @@ func getGithubClientFromEnvironment(config *config.Config, env *models.Environme
 		int64(env.GitInstallationID),
 		config.ServerConf.GithubAppSecret,
 	)
-
 	if err != nil {
 		return nil, fmt.Errorf("error in creating github client from preview environment: %w", err)
 	}
@@ -62,7 +60,6 @@ func isGithubPRClosed(
 	ghPR, _, err := client.PullRequests.Get(
 		context.Background(), owner, name, prNumber,
 	)
-
 	if err != nil {
 		return false, fmt.Errorf("%v: %w", errGithubAPI, err)
 	}

+ 0 - 6
api/server/handlers/environment/create.go

@@ -58,7 +58,6 @@ func (c *CreateEnvironmentHandler) ServeHTTP(w http.ResponseWriter, r *http.Requ
 
 	// create a random webhook id
 	webhookUID, err := encryption.GenerateRandomBytes(32)
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(fmt.Errorf("error generating webhook UID for new preview "+
 			"environment: %w", err)))
@@ -91,7 +90,6 @@ func (c *CreateEnvironmentHandler) ServeHTTP(w http.ResponseWriter, r *http.Requ
 
 	// write Github actions files to the repo
 	client, err := getGithubClientFromEnvironment(c.Config(), env)
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
@@ -137,7 +135,6 @@ func (c *CreateEnvironmentHandler) ServeHTTP(w http.ResponseWriter, r *http.Requ
 
 	// generate porter jwt token
 	jwt, err := token.GetTokenForAPI(user.ID, project.ID)
-
 	if err != nil {
 		_, deleteErr := client.Repositories.DeleteHook(context.Background(), owner, name, hook.GetID())
 
@@ -160,7 +157,6 @@ func (c *CreateEnvironmentHandler) ServeHTTP(w http.ResponseWriter, r *http.Requ
 	}
 
 	encoded, err := jwt.EncodeToken(c.Config().TokenConf)
-
 	if err != nil {
 		_, deleteErr := client.Repositories.DeleteHook(context.Background(), owner, name, hook.GetID())
 
@@ -271,7 +267,6 @@ func createWorkflowDispatchForBranch(
 	var errs []error
 
 	client, err := getGithubClientFromEnvironment(config, env)
-
 	if err != nil {
 		errs = append(errs, err)
 		return errs
@@ -298,7 +293,6 @@ func createWorkflowDispatchForBranch(
 				PRBranchFrom:  branch,
 				PRBranchInto:  branch,
 			})
-
 			if err != nil {
 				errs = append(errs, fmt.Errorf("error creating deployment for branch %s: %w", branch, err))
 				return errs

+ 0 - 6
api/server/handlers/environment/create_deployment.go

@@ -54,7 +54,6 @@ func (c *CreateDeploymentHandler) ServeHTTP(w http.ResponseWriter, r *http.Reque
 
 	// read the environment to get the environment id
 	env, err := c.Repo().Environment().ReadEnvironment(project.ID, cluster.ID, uint(ga.InstallationID), owner, name)
-
 	if err != nil {
 		if errors.Is(err, gorm.ErrRecordNotFound) {
 			c.HandleAPIError(w, r, apierrors.NewErrNotFound(
@@ -69,7 +68,6 @@ func (c *CreateDeploymentHandler) ServeHTTP(w http.ResponseWriter, r *http.Reque
 
 	// create deployment on GitHub API
 	client, err := getGithubClientFromEnvironment(c.Config(), env)
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
@@ -77,7 +75,6 @@ func (c *CreateDeploymentHandler) ServeHTTP(w http.ResponseWriter, r *http.Reque
 
 	// add a check for Github PR status
 	prClosed, err := isGithubPRClosed(client, owner, name, int(request.PullRequestID))
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusConflict))
 		return
@@ -91,7 +88,6 @@ func (c *CreateDeploymentHandler) ServeHTTP(w http.ResponseWriter, r *http.Reque
 	}
 
 	ghDeployment, err := createGithubDeployment(client, env, request.PRBranchFrom, request.ActionID)
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusConflict))
 		return
@@ -111,7 +107,6 @@ func (c *CreateDeploymentHandler) ServeHTTP(w http.ResponseWriter, r *http.Reque
 		PRBranchFrom:   request.GitHubMetadata.PRBranchFrom,
 		PRBranchInto:   request.GitHubMetadata.PRBranchInto,
 	})
-
 	if err != nil {
 		// try to delete the GitHub deployment
 		_, err = client.Repositories.DeleteDeployment(
@@ -153,7 +148,6 @@ func createGithubDeployment(
 			RequiredContexts: &requiredContexts,
 		},
 	)
-
 	if err != nil {
 		return nil, fmt.Errorf("%v: %w", errGithubAPI, err)
 	}

+ 127 - 0
api/server/handlers/environment/create_deployment_by_cluster.go

@@ -0,0 +1,127 @@
+package environment
+
+import (
+	"context"
+	"errors"
+	"fmt"
+	"net/http"
+
+	"github.com/porter-dev/porter/api/server/authz"
+	"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/types"
+	"github.com/porter-dev/porter/internal/models"
+	"gorm.io/gorm"
+)
+
+type CreateDeploymentByClusterHandler struct {
+	handlers.PorterHandlerReadWriter
+	authz.KubernetesAgentGetter
+}
+
+func NewCreateDeploymentByClusterHandler(
+	config *config.Config,
+	decoderValidator shared.RequestDecoderValidator,
+	writer shared.ResultWriter,
+) *CreateDeploymentByClusterHandler {
+	return &CreateDeploymentByClusterHandler{
+		PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, decoderValidator, writer),
+		KubernetesAgentGetter:   authz.NewOutOfClusterAgentGetter(config),
+	}
+}
+
+func (c *CreateDeploymentByClusterHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	project, _ := r.Context().Value(types.ProjectScope).(*models.Project)
+	cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
+
+	request := &types.CreateDeploymentRequest{}
+
+	if ok := c.DecodeAndValidate(w, r, request); !ok {
+		return
+	}
+
+	// read the environment to get the environment id
+	env, err := c.Repo().Environment().ReadEnvironmentByOwnerRepoName(
+		project.ID, cluster.ID, request.RepoOwner, request.RepoName,
+	)
+
+	if err != nil {
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			c.HandleAPIError(w, r, apierrors.NewErrNotFound(
+				fmt.Errorf("error creating deployment: %w", errEnvironmentNotFound)),
+			)
+			return
+		}
+
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
+	}
+
+	// create deployment on GitHub API
+	client, err := getGithubClientFromEnvironment(c.Config(), env)
+
+	if err != nil {
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
+	}
+
+	// add a check for Github PR status
+	prClosed, err := isGithubPRClosed(client, request.RepoOwner, request.RepoName, int(request.PullRequestID))
+
+	if err != nil {
+		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusConflict))
+		return
+	}
+
+	if prClosed {
+		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(
+			fmt.Errorf("attempting to create deployment for a closed github PR"), http.StatusConflict,
+		))
+		return
+	}
+
+	ghDeployment, err := createGithubDeployment(client, env, request.PRBranchFrom, request.ActionID)
+
+	if err != nil {
+		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusConflict))
+		return
+	}
+
+	// create the deployment
+	depl, err := c.Repo().Environment().CreateDeployment(&models.Deployment{
+		EnvironmentID:  env.ID,
+		Namespace:      request.Namespace,
+		Status:         types.DeploymentStatusCreating,
+		PullRequestID:  request.PullRequestID,
+		GHDeploymentID: ghDeployment.GetID(),
+		RepoOwner:      request.GitHubMetadata.RepoOwner,
+		RepoName:       request.GitHubMetadata.RepoName,
+		PRName:         request.GitHubMetadata.PRName,
+		CommitSHA:      request.GitHubMetadata.CommitSHA,
+		PRBranchFrom:   request.GitHubMetadata.PRBranchFrom,
+		PRBranchInto:   request.GitHubMetadata.PRBranchInto,
+	})
+
+	if err != nil {
+		// try to delete the GitHub deployment
+		_, err = client.Repositories.DeleteDeployment(
+			context.Background(),
+			env.GitRepoOwner,
+			env.GitRepoName,
+			ghDeployment.GetID(),
+		)
+
+		if err != nil {
+			c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(fmt.Errorf("%v: %w", errGithubAPI, err),
+				http.StatusConflict))
+			return
+		}
+
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(fmt.Errorf("error creating deployment: %w", err)))
+		return
+	}
+
+	c.WriteResult(w, r, depl.ToDeploymentType())
+}

+ 0 - 4
api/server/handlers/environment/delete.go

@@ -49,7 +49,6 @@ func (c *DeleteEnvironmentHandler) ServeHTTP(w http.ResponseWriter, r *http.Requ
 
 	// read the environment to get the environment id
 	env, err := c.Repo().Environment().ReadEnvironment(project.ID, cluster.ID, uint(ga.InstallationID), owner, name)
-
 	if err != nil {
 		if errors.Is(err, gorm.ErrRecordNotFound) {
 			c.HandleAPIError(w, r, apierrors.NewErrNotFound(errEnvironmentNotFound))
@@ -62,14 +61,12 @@ func (c *DeleteEnvironmentHandler) ServeHTTP(w http.ResponseWriter, r *http.Requ
 
 	// delete all corresponding deployments
 	agent, err := c.GetAgent(r, cluster, "")
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
 	}
 
 	depls, err := c.Repo().Environment().ListDeployments(env.ID)
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
@@ -98,7 +95,6 @@ func (c *DeleteEnvironmentHandler) ServeHTTP(w http.ResponseWriter, r *http.Requ
 	}
 
 	client, err := getGithubClientFromEnvironment(c.Config(), env)
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return

+ 0 - 5
api/server/handlers/environment/delete_deployment.go

@@ -47,7 +47,6 @@ func (c *DeleteDeploymentHandler) ServeHTTP(w http.ResponseWriter, r *http.Reque
 
 	// read the deployment
 	depl, err := c.Repo().Environment().ReadDeploymentByID(project.ID, cluster.ID, deplID)
-
 	if err != nil {
 		if errors.Is(err, gorm.ErrRecordNotFound) {
 			c.HandleAPIError(w, r, apierrors.NewErrNotFound(errDeploymentNotFound))
@@ -60,7 +59,6 @@ func (c *DeleteDeploymentHandler) ServeHTTP(w http.ResponseWriter, r *http.Reque
 
 	// delete corresponding namespace
 	agent, err := c.GetAgent(r, cluster, "")
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
@@ -73,7 +71,6 @@ func (c *DeleteDeploymentHandler) ServeHTTP(w http.ResponseWriter, r *http.Reque
 
 	// check that the environment belongs to the project and cluster IDs
 	env, err := c.Repo().Environment().ReadEnvironmentByID(project.ID, cluster.ID, depl.EnvironmentID)
-
 	if err != nil {
 		if errors.Is(err, gorm.ErrRecordNotFound) {
 			c.HandleAPIError(w, r, apierrors.NewErrNotFound(errEnvironmentNotFound))
@@ -97,7 +94,6 @@ func (c *DeleteDeploymentHandler) ServeHTTP(w http.ResponseWriter, r *http.Reque
 	}
 
 	client, err := getGithubClientFromEnvironment(c.Config(), env)
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
@@ -114,7 +110,6 @@ func (c *DeleteDeploymentHandler) ServeHTTP(w http.ResponseWriter, r *http.Reque
 				State: github.String("inactive"),
 			},
 		)
-
 		if err != nil {
 			c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(
 				fmt.Errorf("%v: %w", errGithubAPI, err), http.StatusConflict,

+ 0 - 4
api/server/handlers/environment/enable_pull_request.go

@@ -44,7 +44,6 @@ func (c *EnablePullRequestHandler) ServeHTTP(w http.ResponseWriter, r *http.Requ
 	}
 
 	env, err := c.Repo().Environment().ReadEnvironmentByOwnerRepoName(project.ID, cluster.ID, request.RepoOwner, request.RepoName)
-
 	if err != nil {
 		if errors.Is(err, gorm.ErrRecordNotFound) {
 			c.HandleAPIError(w, r, apierrors.NewErrNotFound(fmt.Errorf("environment not found in cluster and project")))
@@ -94,7 +93,6 @@ func (c *EnablePullRequestHandler) ServeHTTP(w http.ResponseWriter, r *http.Requ
 	}
 
 	client, err := getGithubClientFromEnvironment(c.Config(), env)
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
@@ -102,7 +100,6 @@ func (c *EnablePullRequestHandler) ServeHTTP(w http.ResponseWriter, r *http.Requ
 
 	// add an extra check that the installation has permission to read this pull request
 	pr, _, err := client.PullRequests.Get(r.Context(), env.GitRepoOwner, env.GitRepoName, int(request.Number))
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(fmt.Errorf("%v: %w", errGithubAPI, err),
 			http.StatusConflict))
@@ -165,7 +162,6 @@ func (c *EnablePullRequestHandler) ServeHTTP(w http.ResponseWriter, r *http.Requ
 		PRBranchFrom:  request.BranchFrom,
 		PRBranchInto:  request.BranchInto,
 	})
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return

+ 0 - 8
api/server/handlers/environment/finalize_deployment.go

@@ -62,7 +62,6 @@ func (c *FinalizeDeploymentHandler) ServeHTTP(w http.ResponseWriter, r *http.Req
 
 	// read the environment to get the environment id
 	env, err := c.Repo().Environment().ReadEnvironment(project.ID, cluster.ID, uint(ga.InstallationID), owner, name)
-
 	if err != nil {
 		if errors.Is(err, gorm.ErrRecordNotFound) {
 			c.HandleAPIError(w, r, apierrors.NewErrNotFound(errEnvironmentNotFound))
@@ -120,7 +119,6 @@ func (c *FinalizeDeploymentHandler) ServeHTTP(w http.ResponseWriter, r *http.Req
 	}
 
 	client, err := getGithubClientFromEnvironment(c.Config(), env)
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
@@ -152,7 +150,6 @@ func (c *FinalizeDeploymentHandler) ServeHTTP(w http.ResponseWriter, r *http.Req
 	if !depl.IsBranchDeploy() {
 		// add a check for the PR to be open before creating a comment
 		prClosed, err := isGithubPRClosed(client, owner, name, int(depl.PullRequestID))
-
 		if err != nil {
 			c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(
 				fmt.Errorf("error fetching details of github PR for deployment ID: %d. Error: %w",
@@ -213,7 +210,6 @@ func createOrUpdateComment(
 		if depl.GHPRCommentID == 0 {
 			// create a new comment
 			err := createGithubComment(client, repo, depl, commentBody)
-
 			if err != nil {
 				return err
 			}
@@ -221,13 +217,11 @@ func createOrUpdateComment(
 			err := updateGithubComment(
 				client, depl.RepoOwner, depl.RepoName, depl.GHPRCommentID, commentBody,
 			)
-
 			if err != nil {
 				if strings.Contains(err.Error(), "404") {
 					// perhaps a deleted comment?
 					// create a new comment
 					err := createGithubComment(client, repo, depl, commentBody)
-
 					if err != nil {
 						return fmt.Errorf("invalid github comment ID for deployment with ID: %d. Error creating "+
 							"new comment: %w", depl.ID, err)
@@ -239,7 +233,6 @@ func createOrUpdateComment(
 		}
 	} else {
 		err := createGithubComment(client, repo, depl, commentBody)
-
 		if err != nil {
 			return err
 		}
@@ -263,7 +256,6 @@ func createGithubComment(
 			Body: body,
 		},
 	)
-
 	if err != nil {
 		return fmt.Errorf("error creating new github comment for owner: %s repo %s prNumber: %d. Error: %w",
 			depl.RepoOwner, depl.RepoName, depl.PullRequestID, err)

+ 186 - 0
api/server/handlers/environment/finalize_deployment_by_cluster.go

@@ -0,0 +1,186 @@
+package environment
+
+import (
+	"context"
+	"errors"
+	"fmt"
+	"net/http"
+
+	"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/types"
+	"github.com/porter-dev/porter/internal/models"
+	"gorm.io/gorm"
+)
+
+type FinalizeDeploymentByClusterHandler struct {
+	handlers.PorterHandlerReadWriter
+}
+
+func NewFinalizeDeploymentByClusterHandler(
+	config *config.Config,
+	decoderValidator shared.RequestDecoderValidator,
+	writer shared.ResultWriter,
+) *FinalizeDeploymentByClusterHandler {
+	return &FinalizeDeploymentByClusterHandler{
+		PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, decoderValidator, writer),
+	}
+}
+
+func (c *FinalizeDeploymentByClusterHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	project, _ := r.Context().Value(types.ProjectScope).(*models.Project)
+	cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
+
+	request := &types.FinalizeDeploymentByClusterRequest{}
+
+	if ok := c.DecodeAndValidate(w, r, request); !ok {
+		return
+	}
+
+	if request.Namespace == "" && request.PRNumber == 0 {
+		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(
+			fmt.Errorf("either namespace or pr_number must be present in request body"), http.StatusBadRequest,
+		))
+		return
+	}
+
+	var err error
+
+	// read the environment to get the environment id
+	env, err := c.Repo().Environment().ReadEnvironmentByOwnerRepoName(
+		project.ID, cluster.ID, request.RepoOwner, request.RepoName,
+	)
+
+	if err != nil {
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			c.HandleAPIError(w, r, apierrors.NewErrNotFound(errEnvironmentNotFound))
+			return
+		}
+
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
+	}
+
+	var depl *models.Deployment
+
+	// read the deployment
+	if request.PRNumber != 0 {
+		depl, err = c.Repo().Environment().ReadDeploymentByGitDetails(
+			env.ID, request.RepoOwner, request.RepoName, request.PRNumber,
+		)
+
+		if err != nil {
+			if errors.Is(err, gorm.ErrRecordNotFound) {
+				c.HandleAPIError(w, r, apierrors.NewErrNotFound(errDeploymentNotFound))
+				return
+			}
+
+			c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+			return
+		}
+	} else if request.Namespace != "" {
+		depl, err = c.Repo().Environment().ReadDeployment(env.ID, request.Namespace)
+
+		if err != nil {
+			if errors.Is(err, gorm.ErrRecordNotFound) {
+				c.HandleAPIError(w, r, apierrors.NewErrNotFound(errDeploymentNotFound))
+				return
+			}
+
+			c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+			return
+		}
+	}
+
+	if depl == nil {
+		c.HandleAPIError(w, r, apierrors.NewErrNotFound(errDeploymentNotFound))
+		return
+	}
+
+	depl.Subdomain = request.Subdomain
+	depl.Status = types.DeploymentStatusCreated
+	depl.LastErrors = ""
+
+	// update the deployment
+	depl, err = c.Repo().Environment().UpdateDeployment(depl)
+
+	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
+	}
+
+	// Create new deployment status to indicate deployment is ready
+
+	state := "success"
+	env_url := depl.Subdomain
+
+	deploymentStatusRequest := github.DeploymentStatusRequest{
+		State:          &state,
+		EnvironmentURL: &env_url,
+	}
+
+	_, _, err = client.Repositories.CreateDeploymentStatus(
+		context.Background(),
+		env.GitRepoOwner,
+		env.GitRepoName,
+		depl.GHDeploymentID,
+		&deploymentStatusRequest,
+	)
+
+	if err != nil {
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
+	}
+
+	if !depl.IsBranchDeploy() {
+		// add a check for the PR to be open before creating a comment
+		prClosed, err := isGithubPRClosed(client, request.RepoOwner, request.RepoName, int(depl.PullRequestID))
+
+		if err != nil {
+			c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(
+				fmt.Errorf("error fetching details of github PR for deployment ID: %d. Error: %w",
+					depl.ID, err), http.StatusConflict,
+			))
+			return
+		}
+
+		if prClosed {
+			c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(fmt.Errorf("Github PR has been closed"),
+				http.StatusConflict))
+			return
+		}
+
+		commentBody := "## Porter Preview Environments\n"
+
+		if depl.Subdomain == "" {
+			commentBody += fmt.Sprintf(
+				"✅ The latest SHA ([`%s`](https://github.com/%s/%s/commit/%s)) has been successfully deployed.",
+				depl.CommitSHA, depl.RepoOwner, depl.RepoName, depl.CommitSHA,
+			)
+		} else {
+			commentBody += fmt.Sprintf(
+				"✅ The latest SHA ([`%s`](https://github.com/%s/%s/commit/%s)) has been successfully deployed to %s",
+				depl.CommitSHA, depl.RepoOwner, depl.RepoName, depl.CommitSHA, depl.Subdomain,
+			)
+		}
+
+		err = createOrUpdateComment(client, c.Repo(), env.NewCommentsDisabled, depl, github.String(commentBody))
+
+		if err != nil {
+			c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+			return
+		}
+	}
+
+	c.WriteResult(w, r, depl.ToDeploymentType())
+}

+ 0 - 4
api/server/handlers/environment/finalize_deployment_with_errors.go

@@ -68,7 +68,6 @@ func (c *FinalizeDeploymentWithErrorsHandler) ServeHTTP(w http.ResponseWriter, r
 
 	// read the environment to get the environment id
 	env, err := c.Repo().Environment().ReadEnvironment(project.ID, cluster.ID, uint(ga.InstallationID), owner, name)
-
 	if err != nil {
 		if errors.Is(err, gorm.ErrRecordNotFound) {
 			c.HandleAPIError(w, r, apierrors.NewErrNotFound(errEnvironmentNotFound))
@@ -114,7 +113,6 @@ func (c *FinalizeDeploymentWithErrorsHandler) ServeHTTP(w http.ResponseWriter, r
 	}
 
 	client, err := getGithubClientFromEnvironment(c.Config(), env)
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
@@ -145,7 +143,6 @@ func (c *FinalizeDeploymentWithErrorsHandler) ServeHTTP(w http.ResponseWriter, r
 	if !depl.IsBranchDeploy() {
 		// add a check for the PR to be open before creating a comment
 		prClosed, err := isGithubPRClosed(client, owner, name, int(depl.PullRequestID))
-
 		if err != nil {
 			c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusConflict))
 			return
@@ -159,7 +156,6 @@ func (c *FinalizeDeploymentWithErrorsHandler) ServeHTTP(w http.ResponseWriter, r
 
 		workflowRun, err := commonutils.GetLatestWorkflowRun(client, depl.RepoOwner, depl.RepoName,
 			fmt.Sprintf("porter_%s_env.yml", env.Name), depl.PRBranchFrom)
-
 		if err != nil {
 			c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 			return

+ 205 - 0
api/server/handlers/environment/finalize_deployment_with_errors_by_cluster.go

@@ -0,0 +1,205 @@
+package environment
+
+import (
+	"context"
+	"errors"
+	"fmt"
+	"net/http"
+	"strings"
+
+	"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/commonutils"
+	"github.com/porter-dev/porter/api/server/shared/config"
+	"github.com/porter-dev/porter/api/types"
+	"github.com/porter-dev/porter/internal/models"
+	"gorm.io/gorm"
+)
+
+type FinalizeDeploymentWithErrorsByClusterHandler struct {
+	handlers.PorterHandlerReadWriter
+}
+
+func NewFinalizeDeploymentWithErrorsByClusterHandler(
+	config *config.Config,
+	decoderValidator shared.RequestDecoderValidator,
+	writer shared.ResultWriter,
+) *FinalizeDeploymentWithErrorsByClusterHandler {
+	return &FinalizeDeploymentWithErrorsByClusterHandler{
+		PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, decoderValidator, writer),
+	}
+}
+
+func (c *FinalizeDeploymentWithErrorsByClusterHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	project, _ := r.Context().Value(types.ProjectScope).(*models.Project)
+	cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
+
+	request := &types.FinalizeDeploymentWithErrorsByClusterRequest{}
+
+	if ok := c.DecodeAndValidate(w, r, request); !ok {
+		return
+	}
+
+	if request.Namespace == "" && request.PRNumber == 0 {
+		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(
+			fmt.Errorf("either namespace or pr_number must be present in request body"), http.StatusBadRequest,
+		))
+		return
+	}
+
+	if len(request.Errors) == 0 {
+		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(
+			fmt.Errorf("at least one error is required to report"), http.StatusPreconditionFailed,
+		))
+		return
+	}
+
+	var err error
+
+	// read the environment to get the environment id
+	env, err := c.Repo().Environment().ReadEnvironmentByOwnerRepoName(
+		project.ID, cluster.ID, request.RepoOwner, request.RepoName,
+	)
+
+	if err != nil {
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			c.HandleAPIError(w, r, apierrors.NewErrNotFound(errEnvironmentNotFound))
+			return
+		}
+
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
+	}
+
+	var depl *models.Deployment
+
+	// read the deployment
+	if request.PRNumber != 0 {
+		depl, err = c.Repo().Environment().ReadDeploymentByGitDetails(
+			env.ID, request.RepoOwner, request.RepoName, request.PRNumber,
+		)
+
+		if err != nil {
+			if errors.Is(err, gorm.ErrRecordNotFound) {
+				c.HandleAPIError(w, r, apierrors.NewErrNotFound(errDeploymentNotFound))
+				return
+			}
+
+			c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+			return
+		}
+	} else if request.Namespace != "" {
+		depl, err = c.Repo().Environment().ReadDeployment(env.ID, request.Namespace)
+
+		if err != nil {
+			if errors.Is(err, gorm.ErrRecordNotFound) {
+				c.HandleAPIError(w, r, apierrors.NewErrNotFound(errDeploymentNotFound))
+				return
+			}
+
+			c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+			return
+		}
+	}
+
+	if depl == nil {
+		c.HandleAPIError(w, r, apierrors.NewErrNotFound(errDeploymentNotFound))
+		return
+	}
+
+	client, err := getGithubClientFromEnvironment(c.Config(), env)
+
+	if err != nil {
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
+	}
+
+	depl.Status = types.DeploymentStatusFailed
+
+	var lastErrors []string
+
+	for resName, errString := range request.Errors {
+		lastErrors = append(lastErrors, fmt.Sprintf("%s: %s", resName, errString))
+	}
+
+	depl.LastErrors = strings.Join(lastErrors, ",")
+
+	// we do not care of the error in this case because the list deployments endpoint
+	// talks to the github API to fetch the deployment status correctly
+	_, _ = c.Repo().Environment().UpdateDeployment(depl)
+
+	// FIXME: ignore the status of this API call for now
+	client.Repositories.CreateDeploymentStatus(
+		context.Background(), request.RepoOwner, request.RepoName, depl.GHDeploymentID, &github.DeploymentStatusRequest{
+			State:       github.String("failure"),
+			Description: github.String("one or more resources failed to build"),
+		},
+	)
+
+	if !depl.IsBranchDeploy() {
+		// add a check for the PR to be open before creating a comment
+		prClosed, err := isGithubPRClosed(client, request.RepoOwner, request.RepoName, int(depl.PullRequestID))
+
+		if err != nil {
+			c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusConflict))
+			return
+		}
+
+		if prClosed {
+			c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(fmt.Errorf("github PR has been closed"),
+				http.StatusConflict))
+			return
+		}
+
+		workflowRun, err := commonutils.GetLatestWorkflowRun(client, depl.RepoOwner, depl.RepoName,
+			fmt.Sprintf("porter_%s_env.yml", env.Name), depl.PRBranchFrom)
+
+		if err != nil {
+			c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+			return
+		}
+
+		commentBody := fmt.Sprintf(
+			"## Porter Preview Environments\n"+
+				"❌ Errors encountered while deploying the changes\n"+
+				"||Deployment Information|\n"+
+				"|-|-|\n"+
+				"| Latest SHA | [`%s`](https://github.com/%s/%s/commit/%s) |\n"+
+				"| Build Logs | %s |\n",
+			depl.CommitSHA, depl.RepoOwner, depl.RepoName, depl.CommitSHA, workflowRun.GetHTMLURL(),
+		)
+
+		if len(request.SuccessfulResources) > 0 {
+			commentBody += "#### Successfully deployed resources\n"
+
+			for _, res := range request.SuccessfulResources {
+				if res.ReleaseType == "job" {
+					commentBody += fmt.Sprintf("- [`%s`](%s/jobs/%s/%s/%s?project_id=%d)\n",
+						res.ReleaseName, c.Config().ServerConf.ServerURL, cluster.Name, depl.Namespace,
+						res.ReleaseName, project.ID)
+				} else {
+					commentBody += fmt.Sprintf("- [`%s`](%s/applications/%s/%s/%s?project_id=%d)\n",
+						res.ReleaseName, c.Config().ServerConf.ServerURL, cluster.Name, depl.Namespace,
+						res.ReleaseName, project.ID)
+				}
+			}
+		}
+
+		commentBody += "#### Failed resources\n"
+
+		for res, err := range request.Errors {
+			commentBody += fmt.Sprintf("<details>\n  <summary><code>%s</code></summary>\n\n  **Error:** %s\n</details>\n", res, err)
+		}
+
+		err = createOrUpdateComment(client, c.Repo(), env.NewCommentsDisabled, depl, github.String(commentBody))
+
+		if err != nil {
+			c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+			return
+		}
+	}
+
+	c.WriteResult(w, r, depl.ToDeploymentType())
+}

+ 0 - 1
api/server/handlers/environment/get_deployment_by_env.go

@@ -46,7 +46,6 @@ func (c *GetDeploymentByEnvironmentHandler) ServeHTTP(w http.ResponseWriter, r *
 	}
 
 	env, err := c.Repo().Environment().ReadEnvironmentByID(project.ID, cluster.ID, envID)
-
 	if err != nil {
 		if errors.Is(err, gorm.ErrRecordNotFound) {
 			c.HandleAPIError(w, r, apierrors.NewErrNotFound(errEnvironmentNotFound))

+ 0 - 1
api/server/handlers/environment/get_environment.go

@@ -40,7 +40,6 @@ func (c *GetEnvironmentHandler) ServeHTTP(w http.ResponseWriter, r *http.Request
 	}
 
 	env, err := c.Repo().Environment().ReadEnvironmentByID(project.ID, cluster.ID, envID)
-
 	if err != nil {
 		if errors.Is(err, gorm.ErrRecordNotFound) {
 			c.HandleAPIError(w, r, apierrors.NewErrNotFound(fmt.Errorf("no such environment with ID: %d", envID)))

+ 0 - 2
api/server/handlers/environment/list.go

@@ -30,7 +30,6 @@ func (c *ListEnvironmentHandler) ServeHTTP(w http.ResponseWriter, r *http.Reques
 	cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
 
 	envs, err := c.Repo().Environment().ListEnvironments(project.ID, cluster.ID)
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(
 			fmt.Errorf("error listing environments"), http.StatusInternalServerError, err.Error(),
@@ -44,7 +43,6 @@ func (c *ListEnvironmentHandler) ServeHTTP(w http.ResponseWriter, r *http.Reques
 		environment := env.ToEnvironmentType()
 
 		depls, err := c.Repo().Environment().ListDeployments(env.ID)
-
 		if err != nil {
 			c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(
 				fmt.Errorf("error listing environments: error listing deployments for environment ID %d", env.ID),

+ 0 - 1
api/server/handlers/environment/list_deployments.go

@@ -62,7 +62,6 @@ func (c *ListDeploymentsHandler) ServeHTTP(w http.ResponseWriter, r *http.Reques
 	}
 
 	depls, err := c.Repo().Environment().ListDeployments(env.ID)
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return

+ 0 - 11
api/server/handlers/environment/list_deployments_by_cluster.go

@@ -45,7 +45,6 @@ func (c *ListDeploymentsByClusterHandler) ServeHTTP(w http.ResponseWriter, r *ht
 
 	if req.EnvironmentID == 0 {
 		depls, err := c.Repo().Environment().ListDeploymentsByCluster(project.ID, cluster.ID)
-
 		if err != nil {
 			c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 			return
@@ -60,7 +59,6 @@ func (c *ListDeploymentsByClusterHandler) ServeHTTP(w http.ResponseWriter, r *ht
 			)] = true
 
 			env, err := c.Repo().Environment().ReadEnvironmentByID(project.ID, cluster.ID, deployment.EnvironmentID)
-
 			if err != nil {
 				c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 				return
@@ -78,7 +76,6 @@ func (c *ListDeploymentsByClusterHandler) ServeHTTP(w http.ResponseWriter, r *ht
 
 		for _, deployment := range deployments {
 			env, err := c.Repo().Environment().ReadEnvironmentByID(project.ID, cluster.ID, deployment.EnvironmentID)
-
 			if err != nil {
 				c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 				return
@@ -86,7 +83,6 @@ func (c *ListDeploymentsByClusterHandler) ServeHTTP(w http.ResponseWriter, r *ht
 
 			if _, ok := envToGithubClientMap[env.ID]; !ok {
 				client, err := getGithubClientFromEnvironment(c.Config(), env)
-
 				if err != nil {
 					c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 					return
@@ -105,7 +101,6 @@ func (c *ListDeploymentsByClusterHandler) ServeHTTP(w http.ResponseWriter, r *ht
 		wg.Wait()
 
 		envList, err := c.Repo().Environment().ListEnvironments(project.ID, cluster.ID)
-
 		if err != nil {
 			c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 			return
@@ -114,7 +109,6 @@ func (c *ListDeploymentsByClusterHandler) ServeHTTP(w http.ResponseWriter, r *ht
 		for _, env := range envList {
 			if _, ok := envToGithubClientMap[env.ID]; !ok {
 				client, err := getGithubClientFromEnvironment(c.Config(), env)
-
 				if err != nil {
 					c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 					return
@@ -124,7 +118,6 @@ func (c *ListDeploymentsByClusterHandler) ServeHTTP(w http.ResponseWriter, r *ht
 			}
 
 			prs, err := fetchOpenPullRequests(r.Context(), c.Config(), envToGithubClientMap[env.ID], env, deplInfoMap)
-
 			if err != nil {
 				c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 				return
@@ -134,14 +127,12 @@ func (c *ListDeploymentsByClusterHandler) ServeHTTP(w http.ResponseWriter, r *ht
 		}
 	} else {
 		env, err := c.Repo().Environment().ReadEnvironmentByID(project.ID, cluster.ID, req.EnvironmentID)
-
 		if err != nil {
 			c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 			return
 		}
 
 		depls, err := c.Repo().Environment().ListDeployments(env.ID)
-
 		if err != nil {
 			c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 			return
@@ -150,7 +141,6 @@ func (c *ListDeploymentsByClusterHandler) ServeHTTP(w http.ResponseWriter, r *ht
 		deplInfoMap := make(map[string]bool)
 
 		client, err := getGithubClientFromEnvironment(c.Config(), env)
-
 		if err != nil {
 			c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 			return
@@ -181,7 +171,6 @@ func (c *ListDeploymentsByClusterHandler) ServeHTTP(w http.ResponseWriter, r *ht
 		wg.Wait()
 
 		prs, err := fetchOpenPullRequests(r.Context(), c.Config(), client, env, deplInfoMap)
-
 		if err != nil {
 			c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 			return

+ 0 - 4
api/server/handlers/environment/reenable_deployment.go

@@ -44,7 +44,6 @@ func (c *ReenableDeploymentHandler) ServeHTTP(w http.ResponseWriter, r *http.Req
 	}
 
 	depl, err := c.Repo().Environment().ReadDeploymentByID(project.ID, cluster.ID, deplID)
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
@@ -58,14 +57,12 @@ func (c *ReenableDeploymentHandler) ServeHTTP(w http.ResponseWriter, r *http.Req
 	}
 
 	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
@@ -74,7 +71,6 @@ func (c *ReenableDeploymentHandler) ServeHTTP(w http.ResponseWriter, r *http.Req
 	if !depl.IsBranchDeploy() {
 		// add a check for the PR to be open before creating a comment
 		prClosed, err := isGithubPRClosed(client, depl.RepoOwner, depl.RepoName, int(depl.PullRequestID))
-
 		if err != nil {
 			c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(
 				fmt.Errorf("error fetching details of github PR for deployment ID: %d. Error: %w",

+ 0 - 1
api/server/handlers/environment/toggle_new_comment.go

@@ -50,7 +50,6 @@ func (c *ToggleNewCommentHandler) ServeHTTP(w http.ResponseWriter, r *http.Reque
 	}
 
 	env, err := c.Repo().Environment().ReadEnvironmentByID(project.ID, cluster.ID, environmentID)
-
 	if err != nil {
 		if errors.Is(err, gorm.ErrRecordNotFound) {
 			c.HandleAPIError(w, r, apierrors.NewErrNotFound(fmt.Errorf("no such environment with ID: %d", environmentID)))

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

@@ -45,7 +45,6 @@ func (c *TriggerDeploymentWorkflowHandler) ServeHTTP(w http.ResponseWriter, r *h
 	}
 
 	depl, err := c.Repo().Environment().ReadDeploymentByID(project.ID, cluster.ID, deplID)
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
@@ -59,14 +58,12 @@ func (c *TriggerDeploymentWorkflowHandler) ServeHTTP(w http.ResponseWriter, r *h
 	}
 
 	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
@@ -75,7 +72,6 @@ func (c *TriggerDeploymentWorkflowHandler) ServeHTTP(w http.ResponseWriter, r *h
 	if !depl.IsBranchDeploy() {
 		// add a check for the PR to be open before creating a comment
 		prClosed, err := isGithubPRClosed(client, depl.RepoOwner, depl.RepoName, int(depl.PullRequestID))
-
 		if err != nil {
 			c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(
 				fmt.Errorf("error fetching details of github PR for deployment ID: %d. Error: %w",

+ 0 - 4
api/server/handlers/environment/update_deployment.go

@@ -61,7 +61,6 @@ func (c *UpdateDeploymentHandler) ServeHTTP(w http.ResponseWriter, r *http.Reque
 
 	// read the environment to get the environment id
 	env, err := c.Repo().Environment().ReadEnvironment(project.ID, cluster.ID, uint(ga.InstallationID), owner, name)
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
@@ -109,7 +108,6 @@ func (c *UpdateDeploymentHandler) ServeHTTP(w http.ResponseWriter, r *http.Reque
 
 	// create deployment on GitHub API
 	client, err := getGithubClientFromEnvironment(c.Config(), env)
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
@@ -118,7 +116,6 @@ func (c *UpdateDeploymentHandler) ServeHTTP(w http.ResponseWriter, r *http.Reque
 	if !depl.IsBranchDeploy() {
 		// add a check for the PR to be open before creating a comment
 		prClosed, err := isGithubPRClosed(client, owner, name, int(depl.PullRequestID))
-
 		if err != nil {
 			c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(
 				fmt.Errorf("error fetching details of github PR for deployment ID: %d. Error: %w",
@@ -135,7 +132,6 @@ func (c *UpdateDeploymentHandler) ServeHTTP(w http.ResponseWriter, r *http.Reque
 	}
 
 	ghDeployment, err := createGithubDeployment(client, env, request.PRBranchFrom, request.ActionID)
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return

+ 154 - 0
api/server/handlers/environment/update_deployment_by_cluster.go

@@ -0,0 +1,154 @@
+package environment
+
+import (
+	"errors"
+	"fmt"
+	"net/http"
+
+	"github.com/porter-dev/porter/api/server/authz"
+	"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/types"
+	"github.com/porter-dev/porter/internal/models"
+	"gorm.io/gorm"
+)
+
+type UpdateDeploymentByClusterHandler struct {
+	handlers.PorterHandlerReadWriter
+	authz.KubernetesAgentGetter
+}
+
+func NewUpdateDeploymentByClusterHandler(
+	config *config.Config,
+	decoderValidator shared.RequestDecoderValidator,
+	writer shared.ResultWriter,
+) *UpdateDeploymentByClusterHandler {
+	return &UpdateDeploymentByClusterHandler{
+		PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, decoderValidator, writer),
+		KubernetesAgentGetter:   authz.NewOutOfClusterAgentGetter(config),
+	}
+}
+
+func (c *UpdateDeploymentByClusterHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	project, _ := r.Context().Value(types.ProjectScope).(*models.Project)
+	cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
+
+	request := &types.UpdateDeploymentByClusterRequest{}
+
+	if ok := c.DecodeAndValidate(w, r, request); !ok {
+		return
+	}
+
+	if request.Namespace == "" && request.PRNumber == 0 && request.PRBranchFrom == "" {
+		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(
+			fmt.Errorf("either namespace, pr_number or pr_branch_from must be present in request body"), http.StatusBadRequest,
+		))
+		return
+	}
+
+	var err error
+
+	// read the environment to get the environment id
+	env, err := c.Repo().Environment().ReadEnvironmentByOwnerRepoName(
+		project.ID, cluster.ID, request.RepoOwner, request.RepoName,
+	)
+
+	if err != nil {
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
+	}
+
+	depl, err := c.Repo().Environment().ReadDeploymentForBranch(
+		env.ID, request.RepoOwner, request.RepoName, request.PRBranchFrom,
+	)
+
+	if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
+	}
+
+	if depl == nil {
+		if request.PRNumber != 0 {
+			depl, err = c.Repo().Environment().ReadDeploymentByGitDetails(
+				env.ID, request.RepoOwner, request.RepoName, request.PRNumber,
+			)
+
+			if err != nil {
+				if errors.Is(err, gorm.ErrRecordNotFound) {
+					c.HandleAPIError(w, r, apierrors.NewErrNotFound(errDeploymentNotFound))
+					return
+				}
+
+				c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+				return
+			}
+		} else if request.Namespace != "" {
+			depl, err = c.Repo().Environment().ReadDeployment(env.ID, request.Namespace)
+
+			if err != nil {
+				if errors.Is(err, gorm.ErrRecordNotFound) {
+					c.HandleAPIError(w, r, apierrors.NewErrNotFound(errDeploymentNotFound))
+					return
+				}
+
+				c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+				return
+			}
+		}
+	}
+
+	if depl == nil {
+		c.HandleAPIError(w, r, apierrors.NewErrNotFound(errDeploymentNotFound))
+		return
+	}
+
+	// create deployment on GitHub API
+	client, err := getGithubClientFromEnvironment(c.Config(), env)
+
+	if err != nil {
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
+	}
+
+	if !depl.IsBranchDeploy() {
+		// add a check for the PR to be open before creating a comment
+		prClosed, err := isGithubPRClosed(client, request.RepoOwner, request.RepoName, int(depl.PullRequestID))
+
+		if err != nil {
+			c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(
+				fmt.Errorf("error fetching details of github PR for deployment ID: %d. Error: %w",
+					depl.ID, err), http.StatusConflict,
+			))
+			return
+		}
+
+		if prClosed {
+			c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(fmt.Errorf("Github PR has been closed"),
+				http.StatusConflict))
+			return
+		}
+	}
+
+	ghDeployment, err := createGithubDeployment(client, env, request.PRBranchFrom, request.ActionID)
+
+	if err != nil {
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
+	}
+
+	depl.Namespace = request.Namespace
+	depl.GHDeploymentID = ghDeployment.GetID()
+	depl.CommitSHA = request.CommitSHA
+
+	// update the deployment
+	depl, err = c.Repo().Environment().UpdateDeployment(depl)
+
+	if err != nil {
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
+	}
+
+	c.WriteResult(w, r, depl.ToDeploymentType())
+}

+ 0 - 3
api/server/handlers/environment/update_deployment_status.go

@@ -61,7 +61,6 @@ func (c *UpdateDeploymentStatusHandler) ServeHTTP(w http.ResponseWriter, r *http
 
 	// read the environment to get the environment id
 	env, err := c.Repo().Environment().ReadEnvironment(project.ID, cluster.ID, uint(ga.InstallationID), owner, name)
-
 	if err != nil {
 		if errors.Is(err, gorm.ErrRecordNotFound) {
 			c.HandleAPIError(w, r, apierrors.NewErrNotFound(errEnvironmentNotFound))
@@ -113,7 +112,6 @@ func (c *UpdateDeploymentStatusHandler) ServeHTTP(w http.ResponseWriter, r *http
 	}
 
 	client, err := getGithubClientFromEnvironment(c.Config(), env)
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(
 			fmt.Errorf("unable to get github client: %w", err), http.StatusConflict,
@@ -124,7 +122,6 @@ func (c *UpdateDeploymentStatusHandler) ServeHTTP(w http.ResponseWriter, r *http
 	if !depl.IsBranchDeploy() {
 		// add a check for the PR to be open before creating a comment
 		prClosed, err := isGithubPRClosed(client, depl.RepoOwner, depl.RepoName, int(depl.PullRequestID))
-
 		if err != nil {
 			c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(
 				fmt.Errorf("error fetching details of github PR for deployment ID: %d. Error: %w",

+ 157 - 0
api/server/handlers/environment/update_deployment_status_by_cluster.go

@@ -0,0 +1,157 @@
+package environment
+
+import (
+	"errors"
+	"fmt"
+	"net/http"
+
+	"github.com/porter-dev/porter/api/server/authz"
+	"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/types"
+	"github.com/porter-dev/porter/internal/models"
+	"gorm.io/gorm"
+)
+
+type UpdateDeploymentStatusByClusterHandler struct {
+	handlers.PorterHandlerReadWriter
+	authz.KubernetesAgentGetter
+}
+
+func NewUpdateDeploymentStatusByClusterHandler(
+	config *config.Config,
+	decoderValidator shared.RequestDecoderValidator,
+	writer shared.ResultWriter,
+) *UpdateDeploymentStatusByClusterHandler {
+	return &UpdateDeploymentStatusByClusterHandler{
+		PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, decoderValidator, writer),
+		KubernetesAgentGetter:   authz.NewOutOfClusterAgentGetter(config),
+	}
+}
+
+func (c *UpdateDeploymentStatusByClusterHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	project, _ := r.Context().Value(types.ProjectScope).(*models.Project)
+	cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
+
+	request := &types.UpdateDeploymentStatusByClusterRequest{}
+
+	if ok := c.DecodeAndValidate(w, r, request); !ok {
+		return
+	}
+
+	if request.Namespace == "" && request.PRNumber == 0 {
+		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(
+			fmt.Errorf("either namespace or pr_number must be present in request body"), http.StatusBadRequest,
+		))
+		return
+	}
+
+	var err error
+
+	// read the environment to get the environment id
+	env, err := c.Repo().Environment().ReadEnvironmentByOwnerRepoName(
+		project.ID, cluster.ID, request.RepoOwner, request.RepoName,
+	)
+
+	if err != nil {
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			c.HandleAPIError(w, r, apierrors.NewErrNotFound(errEnvironmentNotFound))
+			return
+		}
+
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
+	}
+
+	depl, err := c.Repo().Environment().ReadDeploymentForBranch(
+		env.ID, request.RepoOwner, request.RepoName, request.PRBranchFrom,
+	)
+
+	if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
+	}
+
+	if depl == nil {
+		if request.PRNumber != 0 {
+			depl, err = c.Repo().Environment().ReadDeploymentByGitDetails(
+				env.ID, request.RepoOwner, request.RepoName, request.PRNumber,
+			)
+
+			if err != nil {
+				if errors.Is(err, gorm.ErrRecordNotFound) {
+					c.HandleAPIError(w, r, apierrors.NewErrNotFound(errDeploymentNotFound))
+					return
+				}
+
+				c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+				return
+			}
+		} else if request.Namespace != "" {
+			depl, err = c.Repo().Environment().ReadDeployment(env.ID, request.Namespace)
+
+			if err != nil {
+				if errors.Is(err, gorm.ErrRecordNotFound) {
+					c.HandleAPIError(w, r, apierrors.NewErrNotFound(errDeploymentNotFound))
+					return
+				}
+
+				c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+				return
+			}
+		}
+	}
+
+	if depl == nil {
+		c.HandleAPIError(w, r, apierrors.NewErrNotFound(errDeploymentNotFound))
+		return
+	}
+
+	client, err := getGithubClientFromEnvironment(c.Config(), env)
+
+	if err != nil {
+		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(
+			fmt.Errorf("unable to get github client: %w", err), http.StatusConflict,
+		))
+		return
+	}
+
+	if !depl.IsBranchDeploy() {
+		// add a check for the PR to be open before creating a comment
+		prClosed, err := isGithubPRClosed(client, depl.RepoOwner, depl.RepoName, int(depl.PullRequestID))
+
+		if err != nil {
+			c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(
+				fmt.Errorf("error fetching details of github PR for deployment ID: %d. Error: %w",
+					depl.ID, err), http.StatusConflict,
+			))
+			return
+		}
+
+		if prClosed {
+			c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(fmt.Errorf("Github PR has been closed"),
+				http.StatusConflict))
+			return
+		}
+	}
+
+	if depl.Status == types.DeploymentStatusInactive && request.Status != string(types.DeploymentStatusCreating) {
+		// a deployment from "inactive" state can only transition to "creating"
+		c.WriteResult(w, r, depl.ToDeploymentType())
+		return
+	}
+
+	depl.Status = types.DeploymentStatus(request.Status)
+
+	// create the deployment
+	depl, err = c.Repo().Environment().UpdateDeployment(depl)
+
+	if err != nil {
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
+	}
+
+	c.WriteResult(w, r, depl.ToDeploymentType())
+}

+ 0 - 4
api/server/handlers/environment/update_environment_settings.go

@@ -53,7 +53,6 @@ func (c *UpdateEnvironmentSettingsHandler) ServeHTTP(w http.ResponseWriter, r *h
 	}
 
 	env, err := c.Repo().Environment().ReadEnvironmentByID(project.ID, cluster.ID, envID)
-
 	if err != nil {
 		if errors.Is(err, gorm.ErrRecordNotFound) {
 			c.HandleAPIError(w, r, apierrors.NewErrNotFound(fmt.Errorf("no such environment with ID: %d", envID)))
@@ -95,7 +94,6 @@ func (c *UpdateEnvironmentSettingsHandler) ServeHTTP(w http.ResponseWriter, r *h
 	if changed {
 		// let us check if the webhook has access to the "push" event
 		client, err := getGithubClientFromEnvironment(c.Config(), env)
-
 		if err != nil {
 			c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 			return
@@ -104,7 +102,6 @@ func (c *UpdateEnvironmentSettingsHandler) ServeHTTP(w http.ResponseWriter, r *h
 		hook, _, err := client.Repositories.GetHook(
 			context.Background(), env.GitRepoOwner, env.GitRepoName, env.GithubWebhookID,
 		)
-
 		if err != nil {
 			c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 			return
@@ -125,7 +122,6 @@ func (c *UpdateEnvironmentSettingsHandler) ServeHTTP(w http.ResponseWriter, r *h
 			_, _, err := client.Repositories.EditHook(
 				context.Background(), env.GitRepoOwner, env.GitRepoName, env.GithubWebhookID, hook,
 			)
-
 			if err != nil {
 				c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 				return

+ 0 - 4
api/server/handlers/environment/validate_porter_yaml.go

@@ -54,7 +54,6 @@ func (c *ValidatePorterYAMLHandler) ServeHTTP(w http.ResponseWriter, r *http.Req
 	}
 
 	env, err := c.Repo().Environment().ReadEnvironmentByID(project.ID, cluster.ID, envID)
-
 	if err != nil {
 		if errors.Is(err, gorm.ErrRecordNotFound) {
 			c.HandleAPIError(w, r, apierrors.NewErrNotFound(fmt.Errorf("no such environment with ID: %d", envID)))
@@ -66,7 +65,6 @@ func (c *ValidatePorterYAMLHandler) ServeHTTP(w http.ResponseWriter, r *http.Req
 	}
 
 	ghClient, err := getGithubClientFromEnvironment(c.Config(), env)
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
@@ -78,7 +76,6 @@ func (c *ValidatePorterYAMLHandler) ServeHTTP(w http.ResponseWriter, r *http.Req
 
 	if req.Branch == "" { // get the default branch name
 		repo, _, err := ghClient.Repositories.Get(r.Context(), env.GitRepoOwner, env.GitRepoName)
-
 		if err != nil {
 			c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 			return
@@ -106,7 +103,6 @@ func (c *ValidatePorterYAMLHandler) ServeHTTP(w http.ResponseWriter, r *http.Req
 	}
 
 	contents, err := fileContents.GetContent()
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return

+ 2 - 4
api/server/handlers/gitinstallation/get_accounts.go

@@ -35,7 +35,8 @@ func NewGetGithubAppAccountsHandler(
 func (c *GetGithubAppAccountsHandler) getOrgList(ctx context.Context,
 	client *github.Client,
 	orgsChan chan<- *github.Organization,
-	errChan chan<- error) {
+	errChan chan<- error,
+) {
 	defer close(orgsChan)
 	defer close(errChan)
 
@@ -50,7 +51,6 @@ func (c *GetGithubAppAccountsHandler) getOrgList(ctx context.Context,
 				PerPage: 100,
 				Page:    page,
 			})
-
 			if err != nil {
 				errChan <- err
 				return
@@ -71,7 +71,6 @@ func (c *GetGithubAppAccountsHandler) getOrgList(ctx context.Context,
 
 func (c *GetGithubAppAccountsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	tok, err := GetGithubAppOauthTokenFromRequest(c.Config(), r)
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrForbidden(err))
 		return
@@ -110,7 +109,6 @@ resultOrErrorReader:
 	}
 
 	authUser, _, err := client.Users.Get(r.Context(), "")
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return

+ 0 - 2
api/server/handlers/gitinstallation/get_buildpack.go

@@ -72,7 +72,6 @@ func (c *GithubGetBuildpackHandler) ServeHTTP(w http.ResponseWriter, r *http.Req
 	}
 
 	client, err := GetGithubAppClientFromRequest(c.Config(), r)
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
@@ -87,7 +86,6 @@ func (c *GithubGetBuildpackHandler) ServeHTTP(w http.ResponseWriter, r *http.Req
 		request.Dir,
 		&repoContentOptions,
 	)
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return

+ 0 - 2
api/server/handlers/gitinstallation/get_contents.go

@@ -51,7 +51,6 @@ func (c *GithubGetContentsHandler) ServeHTTP(w http.ResponseWriter, r *http.Requ
 	}
 
 	client, err := GetGithubAppClientFromRequest(c.Config(), r)
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
@@ -66,7 +65,6 @@ func (c *GithubGetContentsHandler) ServeHTTP(w http.ResponseWriter, r *http.Requ
 		request.Dir,
 		&repoContentOptions,
 	)
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return

+ 0 - 1
api/server/handlers/gitinstallation/get_permissions.go

@@ -28,7 +28,6 @@ func NewGithubGetPermissionsHandler(
 
 func (c *GithubGetPermissionsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	p, err := GetGithubAppPermissions(c.Config(), r)
-
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return

Einige Dateien werden nicht angezeigt, da zu viele Dateien in diesem Diff geändert wurden.