Просмотр исходного кода

Merge branch 'nico/por-503-show-oomkilled-status-on-expanded-job' of github.com:porter-dev/porter into dev

jnfrati 4 лет назад
Родитель
Сommit
3816416178
47 измененных файлов с 432 добавлено и 428 удалено
  1. 0 2
      .github/workflows/dev.yaml
  2. 0 1
      .github/workflows/staging.yaml
  3. 29 0
      api/client/k8s.go
  4. 64 0
      api/server/authz/gitlab_integration.go
  5. 2 0
      api/server/authz/policy.go
  6. 2 2
      api/server/handlers/environment/create.go
  7. 2 2
      api/server/handlers/environment/create_deployment.go
  8. 2 2
      api/server/handlers/environment/delete.go
  9. 1 1
      api/server/handlers/environment/enable_pull_request.go
  10. 2 2
      api/server/handlers/environment/finalize_deployment.go
  11. 2 2
      api/server/handlers/environment/get_deployment.go
  12. 2 2
      api/server/handlers/environment/list_deployments.go
  13. 2 2
      api/server/handlers/environment/update_deployment.go
  14. 2 2
      api/server/handlers/environment/update_deployment_status.go
  15. 3 2
      api/server/handlers/gitinstallation/get_buildpack.go
  16. 3 2
      api/server/handlers/gitinstallation/get_contents.go
  17. 3 2
      api/server/handlers/gitinstallation/get_procfile.go
  18. 3 2
      api/server/handlers/gitinstallation/get_tarball_url.go
  19. 0 42
      api/server/handlers/gitinstallation/helpers.go
  20. 2 1
      api/server/handlers/gitinstallation/list_branches.go
  21. 1 1
      api/server/handlers/gitinstallation/rerun_workflow.go
  22. 7 0
      api/server/handlers/project_integration/create_gitlab.go
  23. 9 68
      api/server/handlers/project_integration/get_gitlab_repo_buildpack.go
  24. 9 68
      api/server/handlers/project_integration/get_gitlab_repo_contents.go
  25. 9 68
      api/server/handlers/project_integration/get_gitlab_repo_procfile.go
  26. 7 65
      api/server/handlers/project_integration/list_gitlab_repo_branches.go
  27. 47 49
      api/server/handlers/project_integration/list_gitlab_repos.go
  28. 1 1
      api/server/handlers/release/ugprade.go
  29. 1 1
      api/server/handlers/release/update_rollback.go
  30. 18 9
      api/server/router/project_integration.go
  31. 5 0
      api/server/router/router.go
  32. 43 0
      api/server/shared/commonutils/git_utils.go
  33. 3 0
      api/server/shared/config/env/envconfs.go
  34. 2 0
      api/server/shared/config/metadata.go
  35. 13 12
      api/types/policy.go
  36. 1 0
      api/types/request.go
  37. 15 0
      cli/cmd/config.go
  38. 41 2
      cli/cmd/config/config.go
  39. 1 1
      cli/cmd/portforward.go
  40. 1 1
      cli/cmd/run.go
  41. 1 0
      dashboard/src/main/Main.tsx
  42. 48 1
      dashboard/src/main/home/cluster-dashboard/expanded-chart/jobs/ExpandedJobRun.tsx
  43. 12 7
      dashboard/src/main/home/integrations/Integrations.tsx
  44. 2 2
      dashboard/src/main/home/launch/launch-flow/SourcePage.tsx
  45. 6 0
      dashboard/src/shared/Context.tsx
  46. 2 0
      dashboard/src/shared/types.tsx
  47. 1 1
      internal/integrations/ci/actions/steps.go

+ 0 - 2
.github/workflows/dev.yaml

@@ -40,7 +40,6 @@ jobs:
           ENABLE_SENTRY=true
           SENTRY_DSN=${{secrets.SENTRY_DSN}}
           SENTRY_ENV=frontend-development
-          ENABLE_GITLAB=true
           EOL
       - name: Build
         run: |
@@ -118,7 +117,6 @@ jobs:
         ENABLE_SENTRY=true
         SENTRY_DSN=${{secrets.SENTRY_DSN}}
         SENTRY_ENV=frontend-development
-        ENABLE_GITLAB=true
         EOL
     - name: Build
       run: |

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

@@ -47,7 +47,6 @@ jobs:
           ENABLE_SENTRY=true
           SENTRY_DSN=${{secrets.SENTRY_DSN}}
           SENTRY_ENV=frontend-staging
-          ENABLE_GITLAB=true
           EOL
       - name: Build
         run: |

+ 29 - 0
api/client/k8s.go

@@ -3,8 +3,12 @@ package client
 import (
 	"context"
 	"fmt"
+	"io"
+	"os"
 
+	"github.com/fatih/color"
 	"github.com/porter-dev/porter/api/types"
+
 	v1 "k8s.io/api/batch/v1"
 )
 
@@ -55,9 +59,34 @@ func (c *Client) GetKubeconfig(
 	ctx context.Context,
 	projectID uint,
 	clusterID uint,
+	localKubeconfigPath string,
 ) (*types.GetTemporaryKubeconfigResponse, error) {
 	resp := &types.GetTemporaryKubeconfigResponse{}
 
+	if localKubeconfigPath != "" {
+		color.New(color.FgBlue).Printf("using local kubeconfig: %s\n", localKubeconfigPath)
+
+		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
+			}
+
+			resp.Kubeconfig = append(resp.Kubeconfig, data...)
+
+			return resp, nil
+		}
+	}
+
+	color.New(color.FgBlue).Println("using remote kubeconfig")
+
 	err := c.getRequest(
 		fmt.Sprintf(
 			"/projects/%d/clusters/%d/kubeconfig",

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

@@ -0,0 +1,64 @@
+package authz
+
+import (
+	"context"
+	"fmt"
+	"net/http"
+
+	"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"
+	ints "github.com/porter-dev/porter/internal/models/integrations"
+	"gorm.io/gorm"
+)
+
+type GitlabIntegrationScopedFactory struct {
+	config *config.Config
+}
+
+func NewGitlabIntegrationScopedFactory(
+	config *config.Config,
+) *GitlabIntegrationScopedFactory {
+	return &GitlabIntegrationScopedFactory{config}
+}
+
+func (p *GitlabIntegrationScopedFactory) Middleware(next http.Handler) http.Handler {
+	return &GitlabIntegrationScopedMiddleware{next, p.config}
+}
+
+type GitlabIntegrationScopedMiddleware struct {
+	next   http.Handler
+	config *config.Config
+}
+
+func (p *GitlabIntegrationScopedMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	// read the project to check scopes
+	proj, _ := r.Context().Value(types.ProjectScope).(*models.Project)
+
+	// get the integration id from the URL param context
+	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(
+				fmt.Errorf("gitlab integration not found with id %d", integrationID),
+			), true)
+
+			return
+		}
+
+		apierrors.HandleAPIError(p.config.Logger, p.config.Alerter, w, r, apierrors.NewErrInternal(err), true)
+		return
+	}
+
+	ctx := NewGitlabIntegrationContext(r.Context(), gi)
+	r = r.Clone(ctx)
+	p.next.ServeHTTP(w, r)
+}
+
+func NewGitlabIntegrationContext(ctx context.Context, gi *ints.GitlabIntegration) context.Context {
+	return context.WithValue(ctx, types.GitlabIntegrationScope, gi)
+}

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

@@ -132,6 +132,8 @@ func getRequestActionForEndpoint(
 			resource.Name, reqErr = requestutils.GetURLParamString(r, types.URLParamReleaseName)
 		case types.InviteScope:
 			resource.UInt, reqErr = requestutils.GetURLParamUint(r, types.URLParamInviteID)
+		case types.GitlabIntegrationScope:
+			resource.UInt, reqErr = requestutils.GetURLParamUint(r, types.URLParamIntegrationID)
 		}
 
 		if reqErr != nil {

+ 2 - 2
api/server/handlers/environment/create.go

@@ -9,9 +9,9 @@ import (
 	ghinstallation "github.com/bradleyfalzon/ghinstallation/v2"
 	"github.com/google/go-github/v41/github"
 	"github.com/porter-dev/porter/api/server/handlers"
-	"github.com/porter-dev/porter/api/server/handlers/gitinstallation"
 	"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/auth/token"
@@ -41,7 +41,7 @@ func (c *CreateEnvironmentHandler) ServeHTTP(w http.ResponseWriter, r *http.Requ
 	project, _ := r.Context().Value(types.ProjectScope).(*models.Project)
 	cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
 
-	owner, name, ok := gitinstallation.GetOwnerAndNameParams(c, w, r)
+	owner, name, ok := commonutils.GetOwnerAndNameParams(c, w, r)
 
 	if !ok {
 		return

+ 2 - 2
api/server/handlers/environment/create_deployment.go

@@ -8,9 +8,9 @@ import (
 	"github.com/google/go-github/v41/github"
 	"github.com/porter-dev/porter/api/server/authz"
 	"github.com/porter-dev/porter/api/server/handlers"
-	"github.com/porter-dev/porter/api/server/handlers/gitinstallation"
 	"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"
@@ -38,7 +38,7 @@ func (c *CreateDeploymentHandler) ServeHTTP(w http.ResponseWriter, r *http.Reque
 	project, _ := r.Context().Value(types.ProjectScope).(*models.Project)
 	cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
 
-	owner, name, ok := gitinstallation.GetOwnerAndNameParams(c, w, r)
+	owner, name, ok := commonutils.GetOwnerAndNameParams(c, w, r)
 
 	if !ok {
 		return

+ 2 - 2
api/server/handlers/environment/delete.go

@@ -5,9 +5,9 @@ import (
 
 	"github.com/porter-dev/porter/api/server/authz"
 	"github.com/porter-dev/porter/api/server/handlers"
-	"github.com/porter-dev/porter/api/server/handlers/gitinstallation"
 	"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/integrations/ci/actions"
@@ -36,7 +36,7 @@ func (c *DeleteEnvironmentHandler) ServeHTTP(w http.ResponseWriter, r *http.Requ
 	project, _ := r.Context().Value(types.ProjectScope).(*models.Project)
 	cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
 
-	owner, name, ok := gitinstallation.GetOwnerAndNameParams(c, w, r)
+	owner, name, ok := commonutils.GetOwnerAndNameParams(c, w, r)
 
 	if !ok {
 		return

+ 1 - 1
api/server/handlers/environment/enable_pull_request.go

@@ -101,7 +101,7 @@ func (c *EnablePullRequestHandler) ServeHTTP(w http.ResponseWriter, r *http.Requ
 		return
 	}
 
-	namespace := fmt.Sprintf("pr-%d-%s", request.Number, strings.ReplaceAll(env.GitRepoName, "_", "-"))
+	namespace := fmt.Sprintf("pr-%d-%s", request.Number, strings.ToLower(strings.ReplaceAll(env.GitRepoName, "_", "-")))
 
 	// create the deployment
 	depl, err := c.Repo().Environment().CreateDeployment(&models.Deployment{

+ 2 - 2
api/server/handlers/environment/finalize_deployment.go

@@ -7,9 +7,9 @@ import (
 
 	"github.com/google/go-github/v41/github"
 	"github.com/porter-dev/porter/api/server/handlers"
-	"github.com/porter-dev/porter/api/server/handlers/gitinstallation"
 	"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"
@@ -35,7 +35,7 @@ func (c *FinalizeDeploymentHandler) ServeHTTP(w http.ResponseWriter, r *http.Req
 	project, _ := r.Context().Value(types.ProjectScope).(*models.Project)
 	cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
 
-	owner, name, ok := gitinstallation.GetOwnerAndNameParams(c, w, r)
+	owner, name, ok := commonutils.GetOwnerAndNameParams(c, w, r)
 
 	if !ok {
 		return

+ 2 - 2
api/server/handlers/environment/get_deployment.go

@@ -6,9 +6,9 @@ import (
 	"net/http"
 
 	"github.com/porter-dev/porter/api/server/handlers"
-	"github.com/porter-dev/porter/api/server/handlers/gitinstallation"
 	"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"
@@ -35,7 +35,7 @@ func (c *GetDeploymentHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 	project, _ := r.Context().Value(types.ProjectScope).(*models.Project)
 	cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
 
-	owner, name, ok := gitinstallation.GetOwnerAndNameParams(c, w, r)
+	owner, name, ok := commonutils.GetOwnerAndNameParams(c, w, r)
 
 	if !ok {
 		return

+ 2 - 2
api/server/handlers/environment/list_deployments.go

@@ -6,9 +6,9 @@ import (
 	"net/http"
 
 	"github.com/porter-dev/porter/api/server/handlers"
-	"github.com/porter-dev/porter/api/server/handlers/gitinstallation"
 	"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"
@@ -41,7 +41,7 @@ func (c *ListDeploymentsHandler) ServeHTTP(w http.ResponseWriter, r *http.Reques
 		return
 	}
 
-	owner, name, ok := gitinstallation.GetOwnerAndNameParams(c, w, r)
+	owner, name, ok := commonutils.GetOwnerAndNameParams(c, w, r)
 
 	if !ok {
 		return

+ 2 - 2
api/server/handlers/environment/update_deployment.go

@@ -5,9 +5,9 @@ import (
 
 	"github.com/porter-dev/porter/api/server/authz"
 	"github.com/porter-dev/porter/api/server/handlers"
-	"github.com/porter-dev/porter/api/server/handlers/gitinstallation"
 	"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"
@@ -35,7 +35,7 @@ func (c *UpdateDeploymentHandler) ServeHTTP(w http.ResponseWriter, r *http.Reque
 	project, _ := r.Context().Value(types.ProjectScope).(*models.Project)
 	cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
 
-	owner, name, ok := gitinstallation.GetOwnerAndNameParams(c, w, r)
+	owner, name, ok := commonutils.GetOwnerAndNameParams(c, w, r)
 
 	if !ok {
 		return

+ 2 - 2
api/server/handlers/environment/update_deployment_status.go

@@ -5,9 +5,9 @@ import (
 
 	"github.com/porter-dev/porter/api/server/authz"
 	"github.com/porter-dev/porter/api/server/handlers"
-	"github.com/porter-dev/porter/api/server/handlers/gitinstallation"
 	"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"
@@ -35,7 +35,7 @@ func (c *UpdateDeploymentStatusHandler) ServeHTTP(w http.ResponseWriter, r *http
 	project, _ := r.Context().Value(types.ProjectScope).(*models.Project)
 	cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
 
-	owner, name, ok := gitinstallation.GetOwnerAndNameParams(c, w, r)
+	owner, name, ok := commonutils.GetOwnerAndNameParams(c, w, r)
 
 	if !ok {
 		return

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

@@ -11,6 +11,7 @@ import (
 	"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/integrations/buildpacks"
@@ -58,13 +59,13 @@ func (c *GithubGetBuildpackHandler) ServeHTTP(w http.ResponseWriter, r *http.Req
 		return
 	}
 
-	owner, name, ok := GetOwnerAndNameParams(c, w, r)
+	owner, name, ok := commonutils.GetOwnerAndNameParams(c, w, r)
 
 	if !ok {
 		return
 	}
 
-	branch, ok := GetBranch(c, w, r)
+	branch, ok := commonutils.GetBranchParam(c, w, r)
 
 	if !ok {
 		return

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

@@ -9,6 +9,7 @@ import (
 	"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"
 )
@@ -37,13 +38,13 @@ func (c *GithubGetContentsHandler) ServeHTTP(w http.ResponseWriter, r *http.Requ
 		return
 	}
 
-	owner, name, ok := GetOwnerAndNameParams(c, w, r)
+	owner, name, ok := commonutils.GetOwnerAndNameParams(c, w, r)
 
 	if !ok {
 		return
 	}
 
-	branch, ok := GetBranch(c, w, r)
+	branch, ok := commonutils.GetBranchParam(c, w, r)
 
 	if !ok {
 		return

+ 3 - 2
api/server/handlers/gitinstallation/get_procfile.go

@@ -11,6 +11,7 @@ import (
 	"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"
 )
@@ -41,13 +42,13 @@ func (c *GithubGetProcfileHandler) ServeHTTP(w http.ResponseWriter, r *http.Requ
 		return
 	}
 
-	owner, name, ok := GetOwnerAndNameParams(c, w, r)
+	owner, name, ok := commonutils.GetOwnerAndNameParams(c, w, r)
 
 	if !ok {
 		return
 	}
 
-	branch, ok := GetBranch(c, w, r)
+	branch, ok := commonutils.GetBranchParam(c, w, r)
 
 	if !ok {
 		return

+ 3 - 2
api/server/handlers/gitinstallation/get_tarball_url.go

@@ -9,6 +9,7 @@ import (
 	"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"
 )
@@ -29,13 +30,13 @@ func NewGithubGetTarballURLHandler(
 }
 
 func (c *GithubGetTarballURLHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
-	owner, name, ok := GetOwnerAndNameParams(c, w, r)
+	owner, name, ok := commonutils.GetOwnerAndNameParams(c, w, r)
 
 	if !ok {
 		return
 	}
 
-	branch, ok := GetBranch(c, w, r)
+	branch, ok := commonutils.GetBranchParam(c, w, r)
 
 	if !ok {
 		return

+ 0 - 42
api/server/handlers/gitinstallation/helpers.go

@@ -3,14 +3,10 @@ package gitinstallation
 import (
 	"context"
 	"net/http"
-	"net/url"
 
 	ghinstallation "github.com/bradleyfalzon/ghinstallation/v2"
 	"github.com/google/go-github/v41/github"
-	"github.com/porter-dev/porter/api/server/handlers"
-	"github.com/porter-dev/porter/api/server/shared/apierrors"
 	"github.com/porter-dev/porter/api/server/shared/config"
-	"github.com/porter-dev/porter/api/server/shared/requestutils"
 	"github.com/porter-dev/porter/api/types"
 	"github.com/porter-dev/porter/internal/models"
 	"github.com/porter-dev/porter/internal/models/integrations"
@@ -136,41 +132,3 @@ func permissionToString(permission *string) string {
 
 	return *permission
 }
-
-// GetOwnerAndNameParams gets the owner and name ref for the Github repo
-func GetOwnerAndNameParams(c handlers.PorterHandler, w http.ResponseWriter, r *http.Request) (string, string, bool) {
-	owner, reqErr := requestutils.GetURLParamString(r, types.URLParamGitRepoOwner)
-
-	if reqErr != nil {
-		c.HandleAPIError(w, r, reqErr)
-		return "", "", false
-	}
-
-	name, reqErr := requestutils.GetURLParamString(r, types.URLParamGitRepoName)
-
-	if reqErr != nil {
-		c.HandleAPIError(w, r, reqErr)
-		return "", "", false
-	}
-
-	return owner, name, true
-}
-
-// GetBranch gets the unencoded branch
-func GetBranch(c handlers.PorterHandler, w http.ResponseWriter, r *http.Request) (string, bool) {
-	branch, reqErr := requestutils.GetURLParamString(r, types.URLParamGitBranch)
-
-	if reqErr != nil {
-		c.HandleAPIError(w, r, reqErr)
-		return "", false
-	}
-
-	branch, err := url.QueryUnescape(branch)
-
-	if reqErr != nil {
-		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
-		return "", false
-	}
-
-	return branch, true
-}

+ 2 - 1
api/server/handlers/gitinstallation/list_branches.go

@@ -10,6 +10,7 @@ import (
 	"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"
 )
@@ -29,7 +30,7 @@ func NewGithubListBranchesHandler(
 }
 
 func (c *GithubListBranchesHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
-	owner, name, ok := GetOwnerAndNameParams(c, w, r)
+	owner, name, ok := commonutils.GetOwnerAndNameParams(c, w, r)
 
 	if !ok {
 		return

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

@@ -28,7 +28,7 @@ func NewRerunWorkflowHandler(
 }
 
 func (c *RerunWorkflowHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
-	owner, name, ok := GetOwnerAndNameParams(c, w, r)
+	owner, name, ok := commonutils.GetOwnerAndNameParams(c, w, r)
 
 	if !ok {
 		return

+ 7 - 0
api/server/handlers/project_integration/create_gitlab.go

@@ -31,6 +31,13 @@ func NewCreateGitlabIntegration(
 func (p *CreateGitlabIntegration) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	project, _ := r.Context().Value(types.ProjectScope).(*models.Project)
 
+	metadata := p.Config().Metadata
+
+	if !metadata.Gitlab {
+		p.HandleAPIError(w, r, apierrors.NewErrForbidden(fmt.Errorf("gitlab integration endpoints are not enabled")))
+		return
+	}
+
 	request := &types.CreateGitlabRequest{}
 
 	if ok := p.DecodeAndValidate(w, r, request); !ok {

+ 9 - 68
api/server/handlers/project_integration/get_gitlab_repo_buildpack.go

@@ -13,13 +13,11 @@ import (
 	"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/server/shared/requestutils"
 	"github.com/porter-dev/porter/api/types"
 	"github.com/porter-dev/porter/internal/integrations/buildpacks"
 	"github.com/porter-dev/porter/internal/models"
-	"github.com/porter-dev/porter/internal/oauth"
+	ints "github.com/porter-dev/porter/internal/models/integrations"
 	"github.com/xanzy/go-gitlab"
-	"gorm.io/gorm"
 )
 
 type GetGitlabRepoBuildpackHandler struct {
@@ -39,6 +37,7 @@ func NewGetGitlabRepoBuildpackHandler(
 func (p *GetGitlabRepoBuildpackHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	project, _ := r.Context().Value(types.ProjectScope).(*models.Project)
 	user, _ := r.Context().Value(types.UserScope).(*models.User)
+	gi, _ := r.Context().Value(types.GitlabIntegrationScope).(*ints.GitlabIntegration)
 
 	request := &types.GetBuildpackRequest{}
 
@@ -48,87 +47,29 @@ func (p *GetGitlabRepoBuildpackHandler) ServeHTTP(w http.ResponseWriter, r *http
 		return
 	}
 
-	integrationID, reqErr := requestutils.GetURLParamUint(r, "integration_id")
+	owner, name, ok := commonutils.GetOwnerAndNameParams(p, w, r)
 
-	if reqErr != nil {
-		p.HandleAPIError(w, r, apierrors.NewErrInternal(reqErr))
-		return
-	}
-
-	owner, reqErr := requestutils.GetURLParamString(r, types.URLParamGitRepoOwner)
-
-	if reqErr != nil {
-		p.HandleAPIError(w, r, apierrors.NewErrInternal(reqErr))
-		return
-	}
-
-	name, reqErr := requestutils.GetURLParamString(r, types.URLParamGitRepoName)
-
-	if reqErr != nil {
-		p.HandleAPIError(w, r, apierrors.NewErrInternal(reqErr))
-		return
-	}
-
-	branch, reqErr := requestutils.GetURLParamString(r, types.URLParamGitBranch)
-
-	if reqErr != nil {
-		p.HandleAPIError(w, r, apierrors.NewErrInternal(reqErr))
-		return
-	}
-
-	gi, err := p.Repo().GitlabIntegration().ReadGitlabIntegration(project.ID, integrationID)
-
-	if err != nil {
-		if errors.Is(err, gorm.ErrRecordNotFound) {
-			p.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(fmt.Errorf("no gitlab integration with ID: %d", integrationID), http.StatusNotFound))
-			return
-		}
-
-		p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+	if !ok {
 		return
 	}
 
-	giAppOAuth, err := p.Repo().GitlabAppOAuthIntegration().ReadGitlabAppOAuthIntegration(user.ID, project.ID, integrationID)
-
-	if err != nil {
-		if errors.Is(err, gorm.ErrRecordNotFound) {
-			p.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(fmt.Errorf("unauthorized gitlab user"), http.StatusUnauthorized))
-			return
-		}
+	branch, ok := commonutils.GetBranchParam(p, w, r)
 
-		p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+	if !ok {
 		return
 	}
 
-	oauthInt, err := p.Repo().OAuthIntegration().ReadOAuthIntegration(project.ID, giAppOAuth.OAuthIntegrationID)
+	client, err := getGitlabClient(p.Repo(), user.ID, project.ID, gi, p.Config())
 
 	if err != nil {
-		if errors.Is(err, gorm.ErrRecordNotFound) {
-			p.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(fmt.Errorf("unauthorized gitlab user"), http.StatusUnauthorized))
-			return
+		if errors.Is(err, errUnauthorizedGitlabUser) {
+			p.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(errUnauthorizedGitlabUser, http.StatusUnauthorized))
 		}
 
 		p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
 	}
 
-	accessToken, _, err := oauth.GetAccessToken(oauthInt.SharedOAuthModel, commonutils.GetGitlabOAuthConf(
-		p.Config(), gi,
-	), oauth.MakeUpdateGitlabAppOAuthIntegrationFunction(project.ID, giAppOAuth, p.Repo()))
-
-	if err != nil {
-		p.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(fmt.Errorf("invalid gitlab access token"),
-			http.StatusUnauthorized))
-		return
-	}
-
-	client, err := gitlab.NewOAuthClient(accessToken, gitlab.WithBaseURL(gi.InstanceURL))
-
-	if err != nil {
-		p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
-		return
-	}
-
 	dir, err := url.QueryUnescape(request.Dir)
 
 	if err != nil {

+ 9 - 68
api/server/handlers/project_integration/get_gitlab_repo_contents.go

@@ -12,12 +12,10 @@ import (
 	"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/server/shared/requestutils"
 	"github.com/porter-dev/porter/api/types"
 	"github.com/porter-dev/porter/internal/models"
-	"github.com/porter-dev/porter/internal/oauth"
+	ints "github.com/porter-dev/porter/internal/models/integrations"
 	"github.com/xanzy/go-gitlab"
-	"gorm.io/gorm"
 )
 
 type GetGitlabRepoContentsHandler struct {
@@ -37,6 +35,7 @@ func NewGetGitlabRepoContentsHandler(
 func (p *GetGitlabRepoContentsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	project, _ := r.Context().Value(types.ProjectScope).(*models.Project)
 	user, _ := r.Context().Value(types.UserScope).(*models.User)
+	gi, _ := r.Context().Value(types.GitlabIntegrationScope).(*ints.GitlabIntegration)
 
 	request := &types.GetContentsRequest{}
 
@@ -46,31 +45,15 @@ func (p *GetGitlabRepoContentsHandler) ServeHTTP(w http.ResponseWriter, r *http.
 		return
 	}
 
-	integrationID, reqErr := requestutils.GetURLParamUint(r, "integration_id")
+	owner, name, ok := commonutils.GetOwnerAndNameParams(p, w, r)
 
-	if reqErr != nil {
-		p.HandleAPIError(w, r, apierrors.NewErrInternal(reqErr))
-		return
-	}
-
-	owner, reqErr := requestutils.GetURLParamString(r, types.URLParamGitRepoOwner)
-
-	if reqErr != nil {
-		p.HandleAPIError(w, r, apierrors.NewErrInternal(reqErr))
-		return
-	}
-
-	name, reqErr := requestutils.GetURLParamString(r, types.URLParamGitRepoName)
-
-	if reqErr != nil {
-		p.HandleAPIError(w, r, apierrors.NewErrInternal(reqErr))
+	if !ok {
 		return
 	}
 
-	branch, reqErr := requestutils.GetURLParamString(r, types.URLParamGitBranch)
+	branch, ok := commonutils.GetBranchParam(p, w, r)
 
-	if reqErr != nil {
-		p.HandleAPIError(w, r, apierrors.NewErrInternal(reqErr))
+	if !ok {
 		return
 	}
 
@@ -87,59 +70,17 @@ func (p *GetGitlabRepoContentsHandler) ServeHTTP(w http.ResponseWriter, r *http.
 		dir = "."
 	}
 
-	gi, err := p.Repo().GitlabIntegration().ReadGitlabIntegration(project.ID, integrationID)
+	client, err := getGitlabClient(p.Repo(), user.ID, project.ID, gi, p.Config())
 
 	if err != nil {
-		if errors.Is(err, gorm.ErrRecordNotFound) {
-			p.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(fmt.Errorf("no gitlab integration with ID: %d", integrationID), http.StatusNotFound))
-			return
+		if errors.Is(err, errUnauthorizedGitlabUser) {
+			p.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(errUnauthorizedGitlabUser, http.StatusUnauthorized))
 		}
 
 		p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
 	}
 
-	giAppOAuth, err := p.Repo().GitlabAppOAuthIntegration().ReadGitlabAppOAuthIntegration(user.ID, project.ID, integrationID)
-
-	if err != nil {
-		if errors.Is(err, gorm.ErrRecordNotFound) {
-			p.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(fmt.Errorf("unauthorized gitlab user"), http.StatusUnauthorized))
-			return
-		}
-
-		p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
-		return
-	}
-
-	oauthInt, err := p.Repo().OAuthIntegration().ReadOAuthIntegration(project.ID, giAppOAuth.OAuthIntegrationID)
-
-	if err != nil {
-		if errors.Is(err, gorm.ErrRecordNotFound) {
-			p.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(fmt.Errorf("unauthorized gitlab user"), http.StatusUnauthorized))
-			return
-		}
-
-		p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
-		return
-	}
-
-	accessToken, _, err := oauth.GetAccessToken(oauthInt.SharedOAuthModel, commonutils.GetGitlabOAuthConf(
-		p.Config(), gi,
-	), oauth.MakeUpdateGitlabAppOAuthIntegrationFunction(project.ID, giAppOAuth, p.Repo()))
-
-	if err != nil {
-		p.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(fmt.Errorf("invalid gitlab access token"),
-			http.StatusUnauthorized))
-		return
-	}
-
-	client, err := gitlab.NewOAuthClient(accessToken, gitlab.WithBaseURL(gi.InstanceURL))
-
-	if err != nil {
-		p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
-		return
-	}
-
 	tree, resp, err := client.Repositories.ListTree(fmt.Sprintf("%s/%s", owner, name), &gitlab.ListTreeOptions{
 		Path: gitlab.String(dir),
 		Ref:  gitlab.String(branch),

+ 9 - 68
api/server/handlers/project_integration/get_gitlab_repo_procfile.go

@@ -13,12 +13,10 @@ import (
 	"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/server/shared/requestutils"
 	"github.com/porter-dev/porter/api/types"
 	"github.com/porter-dev/porter/internal/models"
-	"github.com/porter-dev/porter/internal/oauth"
+	ints "github.com/porter-dev/porter/internal/models/integrations"
 	"github.com/xanzy/go-gitlab"
-	"gorm.io/gorm"
 )
 
 var procfileRegex = regexp.MustCompile("^([A-Za-z0-9_]+):\\s*(.+)$")
@@ -40,6 +38,7 @@ func NewGetGitlabRepoProcfileHandler(
 func (p *GetGitlabRepoProcfileHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	project, _ := r.Context().Value(types.ProjectScope).(*models.Project)
 	user, _ := r.Context().Value(types.UserScope).(*models.User)
+	gi, _ := r.Context().Value(types.GitlabIntegrationScope).(*ints.GitlabIntegration)
 
 	request := &types.GetProcfileRequest{}
 
@@ -49,31 +48,15 @@ func (p *GetGitlabRepoProcfileHandler) ServeHTTP(w http.ResponseWriter, r *http.
 		return
 	}
 
-	integrationID, reqErr := requestutils.GetURLParamUint(r, "integration_id")
+	owner, name, ok := commonutils.GetOwnerAndNameParams(p, w, r)
 
-	if reqErr != nil {
-		p.HandleAPIError(w, r, apierrors.NewErrInternal(reqErr))
-		return
-	}
-
-	owner, reqErr := requestutils.GetURLParamString(r, types.URLParamGitRepoOwner)
-
-	if reqErr != nil {
-		p.HandleAPIError(w, r, apierrors.NewErrInternal(reqErr))
-		return
-	}
-
-	name, reqErr := requestutils.GetURLParamString(r, types.URLParamGitRepoName)
-
-	if reqErr != nil {
-		p.HandleAPIError(w, r, apierrors.NewErrInternal(reqErr))
+	if !ok {
 		return
 	}
 
-	branch, reqErr := requestutils.GetURLParamString(r, types.URLParamGitBranch)
+	branch, ok := commonutils.GetBranchParam(p, w, r)
 
-	if reqErr != nil {
-		p.HandleAPIError(w, r, apierrors.NewErrInternal(reqErr))
+	if !ok {
 		return
 	}
 
@@ -84,59 +67,17 @@ func (p *GetGitlabRepoProcfileHandler) ServeHTTP(w http.ResponseWriter, r *http.
 		return
 	}
 
-	gi, err := p.Repo().GitlabIntegration().ReadGitlabIntegration(project.ID, integrationID)
+	client, err := getGitlabClient(p.Repo(), user.ID, project.ID, gi, p.Config())
 
 	if err != nil {
-		if errors.Is(err, gorm.ErrRecordNotFound) {
-			p.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(fmt.Errorf("no gitlab integration with ID: %d", integrationID), http.StatusNotFound))
-			return
+		if errors.Is(err, errUnauthorizedGitlabUser) {
+			p.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(errUnauthorizedGitlabUser, http.StatusUnauthorized))
 		}
 
 		p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
 	}
 
-	giAppOAuth, err := p.Repo().GitlabAppOAuthIntegration().ReadGitlabAppOAuthIntegration(user.ID, project.ID, integrationID)
-
-	if err != nil {
-		if errors.Is(err, gorm.ErrRecordNotFound) {
-			p.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(fmt.Errorf("unauthorized gitlab user"), http.StatusUnauthorized))
-			return
-		}
-
-		p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
-		return
-	}
-
-	oauthInt, err := p.Repo().OAuthIntegration().ReadOAuthIntegration(project.ID, giAppOAuth.OAuthIntegrationID)
-
-	if err != nil {
-		if errors.Is(err, gorm.ErrRecordNotFound) {
-			p.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(fmt.Errorf("unauthorized gitlab user"), http.StatusUnauthorized))
-			return
-		}
-
-		p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
-		return
-	}
-
-	accessToken, _, err := oauth.GetAccessToken(oauthInt.SharedOAuthModel, commonutils.GetGitlabOAuthConf(
-		p.Config(), gi,
-	), oauth.MakeUpdateGitlabAppOAuthIntegrationFunction(project.ID, giAppOAuth, p.Repo()))
-
-	if err != nil {
-		p.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(fmt.Errorf("invalid gitlab access token"),
-			http.StatusUnauthorized))
-		return
-	}
-
-	client, err := gitlab.NewOAuthClient(accessToken, gitlab.WithBaseURL(gi.InstanceURL))
-
-	if err != nil {
-		p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
-		return
-	}
-
 	file, resp, err := client.RepositoryFiles.GetRawFile(fmt.Sprintf("%s/%s", owner, name),
 		strings.TrimPrefix(path, "./"), &gitlab.GetRawFileOptions{
 			Ref: gitlab.String(branch),

+ 7 - 65
api/server/handlers/project_integration/list_gitlab_repo_branches.go

@@ -10,12 +10,10 @@ import (
 	"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/server/shared/requestutils"
 	"github.com/porter-dev/porter/api/types"
 	"github.com/porter-dev/porter/internal/models"
-	"github.com/porter-dev/porter/internal/oauth"
+	ints "github.com/porter-dev/porter/internal/models/integrations"
 	"github.com/xanzy/go-gitlab"
-	"gorm.io/gorm"
 )
 
 type ListGitlabRepoBranchesHandler struct {
@@ -35,81 +33,25 @@ func NewListGitlabRepoBranchesHandler(
 func (p *ListGitlabRepoBranchesHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	project, _ := r.Context().Value(types.ProjectScope).(*models.Project)
 	user, _ := r.Context().Value(types.UserScope).(*models.User)
+	gi, _ := r.Context().Value(types.GitlabIntegrationScope).(*ints.GitlabIntegration)
 
-	integrationID, reqErr := requestutils.GetURLParamUint(r, "integration_id")
+	owner, name, ok := commonutils.GetOwnerAndNameParams(p, w, r)
 
-	if reqErr != nil {
-		p.HandleAPIError(w, r, apierrors.NewErrInternal(reqErr))
+	if !ok {
 		return
 	}
 
-	owner, reqErr := requestutils.GetURLParamString(r, types.URLParamGitRepoOwner)
-
-	if reqErr != nil {
-		p.HandleAPIError(w, r, apierrors.NewErrInternal(reqErr))
-		return
-	}
-
-	name, reqErr := requestutils.GetURLParamString(r, types.URLParamGitRepoName)
-
-	if reqErr != nil {
-		p.HandleAPIError(w, r, apierrors.NewErrInternal(reqErr))
-		return
-	}
-
-	gi, err := p.Repo().GitlabIntegration().ReadGitlabIntegration(project.ID, integrationID)
-
-	if err != nil {
-		if errors.Is(err, gorm.ErrRecordNotFound) {
-			p.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(fmt.Errorf("no gitlab integration with ID: %d", integrationID), http.StatusNotFound))
-			return
-		}
-
-		p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
-		return
-	}
-
-	giAppOAuth, err := p.Repo().GitlabAppOAuthIntegration().ReadGitlabAppOAuthIntegration(user.ID, project.ID, integrationID)
+	client, err := getGitlabClient(p.Repo(), user.ID, project.ID, gi, p.Config())
 
 	if err != nil {
-		if errors.Is(err, gorm.ErrRecordNotFound) {
-			p.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(fmt.Errorf("unauthorized gitlab user"), http.StatusUnauthorized))
-			return
+		if errors.Is(err, errUnauthorizedGitlabUser) {
+			p.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(errUnauthorizedGitlabUser, http.StatusUnauthorized))
 		}
 
 		p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
 	}
 
-	oauthInt, err := p.Repo().OAuthIntegration().ReadOAuthIntegration(project.ID, giAppOAuth.OAuthIntegrationID)
-
-	if err != nil {
-		if errors.Is(err, gorm.ErrRecordNotFound) {
-			p.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(fmt.Errorf("unauthorized gitlab user"), http.StatusUnauthorized))
-			return
-		}
-
-		p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
-		return
-	}
-
-	accessToken, _, err := oauth.GetAccessToken(oauthInt.SharedOAuthModel, commonutils.GetGitlabOAuthConf(
-		p.Config(), gi,
-	), oauth.MakeUpdateGitlabAppOAuthIntegrationFunction(project.ID, giAppOAuth, p.Repo()))
-
-	if err != nil {
-		p.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(fmt.Errorf("invalid gitlab access token"),
-			http.StatusUnauthorized))
-		return
-	}
-
-	client, err := gitlab.NewOAuthClient(accessToken, gitlab.WithBaseURL(gi.InstanceURL))
-
-	if err != nil {
-		p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
-		return
-	}
-
 	branches, resp, err := client.Branches.ListBranches(fmt.Sprintf("%s/%s", owner, name), &gitlab.ListBranchesOptions{})
 
 	if resp.StatusCode == http.StatusUnauthorized {

+ 47 - 49
api/server/handlers/project_integration/list_gitlab_repos.go

@@ -10,14 +10,17 @@ import (
 	"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/server/shared/requestutils"
 	"github.com/porter-dev/porter/api/types"
 	"github.com/porter-dev/porter/internal/models"
+	ints "github.com/porter-dev/porter/internal/models/integrations"
 	"github.com/porter-dev/porter/internal/oauth"
+	"github.com/porter-dev/porter/internal/repository"
 	"github.com/xanzy/go-gitlab"
 	"gorm.io/gorm"
 )
 
+var errUnauthorizedGitlabUser = errors.New("unauthorized gitlab user")
+
 type ListGitlabReposHandler struct {
 	handlers.PorterHandlerReadWriter
 }
@@ -35,87 +38,82 @@ func NewListGitlabReposHandler(
 func (p *ListGitlabReposHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	project, _ := r.Context().Value(types.ProjectScope).(*models.Project)
 	user, _ := r.Context().Value(types.UserScope).(*models.User)
+	gi, _ := r.Context().Value(types.GitlabIntegrationScope).(*ints.GitlabIntegration)
+
+	client, err := getGitlabClient(p.Repo(), user.ID, project.ID, gi, p.Config())
 
-	integrationID, reqErr := requestutils.GetURLParamUint(r, "integration_id")
+	if err != nil {
+		if errors.Is(err, errUnauthorizedGitlabUser) {
+			p.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(errUnauthorizedGitlabUser, http.StatusUnauthorized))
+		}
 
-	if reqErr != nil {
-		p.HandleAPIError(w, r, apierrors.NewErrInternal(reqErr))
+		p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
 	}
 
-	gi, err := p.Repo().GitlabIntegration().ReadGitlabIntegration(project.ID, integrationID)
+	giProjects, resp, err := client.Projects.ListProjects(&gitlab.ListProjectsOptions{
+		Simple:     gitlab.Bool(true),
+		Membership: gitlab.Bool(true),
+	})
 
-	if err != nil {
-		if errors.Is(err, gorm.ErrRecordNotFound) {
-			p.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(fmt.Errorf("no gitlab integration with ID: %d", integrationID), http.StatusNotFound))
-			return
-		}
+	if resp.StatusCode == http.StatusUnauthorized {
+		p.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(fmt.Errorf("unauthorized gitlab user"), http.StatusUnauthorized))
+		return
+	}
 
+	if err != nil {
 		p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
 	}
 
-	giAppOAuth, err := p.Repo().GitlabAppOAuthIntegration().ReadGitlabAppOAuthIntegration(user.ID, project.ID, integrationID)
+	var res []string
+
+	for _, giProject := range giProjects {
+		res = append(res, giProject.PathWithNamespace)
+	}
+
+	p.WriteResult(w, r, res)
+}
+
+func getGitlabClient(
+	repo repository.Repository,
+	userID, projectID uint,
+	gi *ints.GitlabIntegration,
+	config *config.Config,
+) (*gitlab.Client, error) {
+	giAppOAuth, err := repo.GitlabAppOAuthIntegration().ReadGitlabAppOAuthIntegration(userID, projectID, gi.ID)
 
 	if err != nil {
 		if errors.Is(err, gorm.ErrRecordNotFound) {
-			p.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(fmt.Errorf("unauthorized gitlab user"), http.StatusUnauthorized))
-			return
+			return nil, errUnauthorizedGitlabUser
 		}
 
-		p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
-		return
+		return nil, err
 	}
 
-	oauthInt, err := p.Repo().OAuthIntegration().ReadOAuthIntegration(project.ID, giAppOAuth.OAuthIntegrationID)
+	oauthInt, err := repo.OAuthIntegration().ReadOAuthIntegration(projectID, giAppOAuth.OAuthIntegrationID)
 
 	if err != nil {
 		if errors.Is(err, gorm.ErrRecordNotFound) {
-			p.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(fmt.Errorf("unauthorized gitlab user"), http.StatusUnauthorized))
-			return
+			return nil, errUnauthorizedGitlabUser
 		}
 
-		p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
-		return
+		return nil, err
 	}
 
 	accessToken, _, err := oauth.GetAccessToken(oauthInt.SharedOAuthModel, commonutils.GetGitlabOAuthConf(
-		p.Config(), gi,
-	), oauth.MakeUpdateGitlabAppOAuthIntegrationFunction(project.ID, giAppOAuth, p.Repo()))
+		config, gi,
+	), oauth.MakeUpdateGitlabAppOAuthIntegrationFunction(projectID, giAppOAuth, repo))
 
 	if err != nil {
-		p.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(fmt.Errorf("invalid gitlab access token"),
-			http.StatusUnauthorized))
-		return
+		return nil, errUnauthorizedGitlabUser
 	}
 
 	client, err := gitlab.NewOAuthClient(accessToken, gitlab.WithBaseURL(gi.InstanceURL))
 
 	if err != nil {
-		p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
-		return
-	}
-
-	giProjects, resp, err := client.Projects.ListProjects(&gitlab.ListProjectsOptions{
-		Simple:     gitlab.Bool(true),
-		Membership: gitlab.Bool(true),
-	})
-
-	if resp.StatusCode == http.StatusUnauthorized {
-		p.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(fmt.Errorf("unauthorized gitlab user"), http.StatusUnauthorized))
-		return
-	}
-
-	if err != nil {
-		p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
-		return
+		return nil, err
 	}
 
-	var res []string
-
-	for _, giProject := range giProjects {
-		res = append(res, giProject.PathWithNamespace)
-	}
-
-	p.WriteResult(w, r, res)
+	return client, nil
 }

+ 1 - 1
api/server/handlers/release/ugprade.go

@@ -195,7 +195,7 @@ func (c *UpgradeReleaseHandler) ServeHTTP(w http.ResponseWriter, r *http.Request
 
 			gitAction := rel.GitActionConfig
 
-			if gitAction != nil && gitAction.ID != 0 {
+			if gitAction != nil && gitAction.ID != 0 && gitAction.GitlabIntegrationID == 0 {
 				gaRunner, err := getGARunner(
 					c.Config(),
 					user.ID,

+ 1 - 1
api/server/handlers/release/update_rollback.go

@@ -74,7 +74,7 @@ func (c *RollbackReleaseHandler) ServeHTTP(w http.ResponseWriter, r *http.Reques
 
 			gitAction := rel.GitActionConfig
 
-			if gitAction != nil && gitAction.ID != 0 {
+			if gitAction != nil && gitAction.ID != 0 && gitAction.GitlabIntegrationID == 0 {
 				gaRunner, err := getGARunner(
 					c.Config(),
 					user.ID,

+ 18 - 9
api/server/router/project_integration.go

@@ -424,11 +424,12 @@ func getProjectIntegrationRoutes(
 			Method: types.HTTPVerbGet,
 			Path: &types.Path{
 				Parent:       basePath,
-				RelativePath: relPath + "/gitlab/{integration_id}/repos",
+				RelativePath: fmt.Sprintf("%s/gitlab/{%s}/repos", relPath, types.URLParamIntegrationID),
 			},
 			Scopes: []types.PermissionScope{
 				types.UserScope,
 				types.ProjectScope,
+				types.GitlabIntegrationScope,
 			},
 		},
 	)
@@ -451,12 +452,14 @@ func getProjectIntegrationRoutes(
 			Verb:   types.APIVerbGet,
 			Method: types.HTTPVerbGet,
 			Path: &types.Path{
-				Parent:       basePath,
-				RelativePath: fmt.Sprintf("%s/gitlab/{integration_id}/repos/{%s}/{%s}/branches", relPath, types.URLParamGitRepoOwner, types.URLParamGitRepoName),
+				Parent: basePath,
+				RelativePath: fmt.Sprintf("%s/gitlab/{%s}/repos/{%s}/{%s}/branches",
+					relPath, types.URLParamIntegrationID, types.URLParamGitRepoOwner, types.URLParamGitRepoName),
 			},
 			Scopes: []types.PermissionScope{
 				types.UserScope,
 				types.ProjectScope,
+				types.GitlabIntegrationScope,
 			},
 		},
 	)
@@ -480,12 +483,14 @@ func getProjectIntegrationRoutes(
 			Method: types.HTTPVerbGet,
 			Path: &types.Path{
 				Parent: basePath,
-				RelativePath: fmt.Sprintf("%s/gitlab/{integration_id}/repos/{%s}/{%s}/{%s}/contents", relPath,
-					types.URLParamGitRepoOwner, types.URLParamGitRepoName, types.URLParamGitBranch),
+				RelativePath: fmt.Sprintf("%s/gitlab/{%s}/repos/{%s}/{%s}/{%s}/contents", relPath,
+					types.URLParamIntegrationID, types.URLParamGitRepoOwner,
+					types.URLParamGitRepoName, types.URLParamGitBranch),
 			},
 			Scopes: []types.PermissionScope{
 				types.UserScope,
 				types.ProjectScope,
+				types.GitlabIntegrationScope,
 			},
 		},
 	)
@@ -509,12 +514,14 @@ func getProjectIntegrationRoutes(
 			Method: types.HTTPVerbGet,
 			Path: &types.Path{
 				Parent: basePath,
-				RelativePath: fmt.Sprintf("%s/gitlab/{integration_id}/repos/{%s}/{%s}/{%s}/buildpack/detect", relPath,
-					types.URLParamGitRepoOwner, types.URLParamGitRepoName, types.URLParamGitBranch),
+				RelativePath: fmt.Sprintf("%s/gitlab/{%s}/repos/{%s}/{%s}/{%s}/buildpack/detect", relPath,
+					types.URLParamIntegrationID, types.URLParamGitRepoOwner,
+					types.URLParamGitRepoName, types.URLParamGitBranch),
 			},
 			Scopes: []types.PermissionScope{
 				types.UserScope,
 				types.ProjectScope,
+				types.GitlabIntegrationScope,
 			},
 		},
 	)
@@ -538,12 +545,14 @@ func getProjectIntegrationRoutes(
 			Method: types.HTTPVerbGet,
 			Path: &types.Path{
 				Parent: basePath,
-				RelativePath: fmt.Sprintf("%s/gitlab/{integration_id}/repos/{%s}/{%s}/{%s}/procfile", relPath,
-					types.URLParamGitRepoOwner, types.URLParamGitRepoName, types.URLParamGitBranch),
+				RelativePath: fmt.Sprintf("%s/gitlab/{%s}/repos/{%s}/{%s}/{%s}/procfile", relPath,
+					types.URLParamIntegrationID, types.URLParamGitRepoOwner,
+					types.URLParamGitRepoName, types.URLParamGitBranch),
 			},
 			Scopes: []types.PermissionScope{
 				types.UserScope,
 				types.ProjectScope,
+				types.GitlabIntegrationScope,
 			},
 		},
 	)

+ 5 - 0
api/server/router/router.go

@@ -217,6 +217,9 @@ func registerRoutes(config *config.Config, routes []*router.Route) {
 	// websocket middleware for upgrading requests
 	websocketMw := middleware.NewWebsocketMiddleware(config)
 
+	// gitlab integration middleware to handle gitlab integrations for a specific project
+	gitlabIntFactory := authz.NewGitlabIntegrationScopedFactory(config)
+
 	for _, route := range routes {
 		atomicGroup := route.Router.Group(nil)
 
@@ -252,6 +255,8 @@ func registerRoutes(config *config.Config, routes []*router.Route) {
 				atomicGroup.Use(operationFactory.Middleware)
 			case types.ReleaseScope:
 				atomicGroup.Use(releaseFactory.Middleware)
+			case types.GitlabIntegrationScope:
+				atomicGroup.Use(gitlabIntFactory.Middleware)
 			}
 		}
 

+ 43 - 0
api/server/shared/commonutils/git_utils.go

@@ -4,9 +4,14 @@ import (
 	"context"
 	"errors"
 	"net/http"
+	"net/url"
 	"time"
 
 	"github.com/google/go-github/v41/github"
+	"github.com/porter-dev/porter/api/server/handlers"
+	"github.com/porter-dev/porter/api/server/shared/apierrors"
+	"github.com/porter-dev/porter/api/server/shared/requestutils"
+	"github.com/porter-dev/porter/api/types"
 )
 
 var ErrNoWorkflowRuns = errors.New("no previous workflow runs found")
@@ -40,3 +45,41 @@ func GetLatestWorkflowRun(client *github.Client, owner, repo, filename, branch s
 
 	return workflowRuns.WorkflowRuns[0], nil
 }
+
+// GetOwnerAndNameParams gets the owner and name ref for the git repo
+func GetOwnerAndNameParams(c handlers.PorterHandler, w http.ResponseWriter, r *http.Request) (string, string, bool) {
+	owner, reqErr := requestutils.GetURLParamString(r, types.URLParamGitRepoOwner)
+
+	if reqErr != nil {
+		c.HandleAPIError(w, r, reqErr)
+		return "", "", false
+	}
+
+	name, reqErr := requestutils.GetURLParamString(r, types.URLParamGitRepoName)
+
+	if reqErr != nil {
+		c.HandleAPIError(w, r, reqErr)
+		return "", "", false
+	}
+
+	return owner, name, true
+}
+
+// GetBranchParam gets the unencoded branch for the git repo
+func GetBranchParam(c handlers.PorterHandler, w http.ResponseWriter, r *http.Request) (string, bool) {
+	branch, reqErr := requestutils.GetURLParamString(r, types.URLParamGitBranch)
+
+	if reqErr != nil {
+		c.HandleAPIError(w, r, reqErr)
+		return "", false
+	}
+
+	branch, err := url.QueryUnescape(branch)
+
+	if reqErr != nil {
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return "", false
+	}
+
+	return branch, true
+}

+ 3 - 0
api/server/shared/config/env/envconfs.go

@@ -99,6 +99,9 @@ type ServerConf struct {
 
 	// Disable filtering for project creation
 	DisableAllowlist bool `env:"DISABLE_ALLOWLIST,default=false"`
+
+	// Enable gitlab integration
+	EnableGitlab bool `env:"ENABLE_GITLAB,default=false"`
 }
 
 // DBConf is the database configuration: if generated from environment variables,

+ 2 - 0
api/server/shared/config/metadata.go

@@ -14,6 +14,7 @@ type Metadata struct {
 	Email              bool   `json:"email"`
 	Analytics          bool   `json:"analytics"`
 	Version            string `json:"version"`
+	Gitlab             bool   `json:"gitlab"`
 }
 
 func MetadataFromConf(sc *env.ServerConf, version string) *Metadata {
@@ -27,6 +28,7 @@ func MetadataFromConf(sc *env.ServerConf, version string) *Metadata {
 		Email:              sc.SendgridAPIKey != "",
 		Analytics:          sc.SegmentClientKey != "",
 		Version:            version,
+		Gitlab:             sc.EnableGitlab,
 	}
 }
 

+ 13 - 12
api/types/policy.go

@@ -5,18 +5,19 @@ import "time"
 type PermissionScope string
 
 const (
-	UserScope            PermissionScope = "user"
-	ProjectScope         PermissionScope = "project"
-	ClusterScope         PermissionScope = "cluster"
-	RegistryScope        PermissionScope = "registry"
-	InviteScope          PermissionScope = "invite"
-	HelmRepoScope        PermissionScope = "helm_repo"
-	InfraScope           PermissionScope = "infra"
-	OperationScope       PermissionScope = "operation"
-	GitInstallationScope PermissionScope = "git_installation"
-	NamespaceScope       PermissionScope = "namespace"
-	SettingsScope        PermissionScope = "settings"
-	ReleaseScope         PermissionScope = "release"
+	UserScope              PermissionScope = "user"
+	ProjectScope           PermissionScope = "project"
+	ClusterScope           PermissionScope = "cluster"
+	RegistryScope          PermissionScope = "registry"
+	InviteScope            PermissionScope = "invite"
+	HelmRepoScope          PermissionScope = "helm_repo"
+	InfraScope             PermissionScope = "infra"
+	OperationScope         PermissionScope = "operation"
+	GitInstallationScope   PermissionScope = "git_installation"
+	NamespaceScope         PermissionScope = "namespace"
+	SettingsScope          PermissionScope = "settings"
+	ReleaseScope           PermissionScope = "release"
+	GitlabIntegrationScope PermissionScope = "gitlab_integration"
 )
 
 type NameOrUInt struct {

+ 1 - 0
api/types/request.go

@@ -45,6 +45,7 @@ const (
 	URLParamReleaseName       URLParam = "name"
 	URLParamReleaseVersion    URLParam = "version"
 	URLParamWildcard          URLParam = "*"
+	URLParamIntegrationID     URLParam = "integration_id"
 )
 
 type Path struct {

+ 15 - 0
cli/cmd/config.go

@@ -154,6 +154,20 @@ var configSetHostCmd = &cobra.Command{
 	},
 }
 
+var configSetKubeconfigCmd = &cobra.Command{
+	Use:   "set-kubeconfig [kubeconfig-path]",
+	Args:  cobra.ExactArgs(1),
+	Short: "Saves the path to kubeconfig in the default configuration",
+	Run: func(cmd *cobra.Command, args []string) {
+		err := cliConf.SetKubeconfig(args[0])
+
+		if err != nil {
+			color.New(color.FgRed).Printf("An error occurred: %v\n", err)
+			os.Exit(1)
+		}
+	},
+}
+
 func init() {
 	rootCmd.AddCommand(configCmd)
 
@@ -162,6 +176,7 @@ func init() {
 	configCmd.AddCommand(configSetHostCmd)
 	configCmd.AddCommand(configSetRegistryCmd)
 	configCmd.AddCommand(configSetHelmRepoCmd)
+	configCmd.AddCommand(configSetKubeconfigCmd)
 }
 
 func printConfig() error {

+ 41 - 2
cli/cmd/config/config.go

@@ -1,6 +1,8 @@
 package config
 
 import (
+	"errors"
+	"fmt"
 	"io/ioutil"
 	"os"
 	"path/filepath"
@@ -32,8 +34,9 @@ type CLIConfig struct {
 
 	Token string `yaml:"token"`
 
-	Registry uint `yaml:"registry"`
-	HelmRepo uint `yaml:"helm_repo"`
+	Registry   uint   `yaml:"registry"`
+	HelmRepo   uint   `yaml:"helm_repo"`
+	Kubeconfig string `yaml:"kubeconfig"`
 }
 
 // InitAndLoadConfig populates the config object with the following precedence rules:
@@ -209,6 +212,12 @@ func (c *CLIConfig) SetHost(host string) error {
 }
 
 func (c *CLIConfig) SetProject(projectID uint) error {
+	if config.Kubeconfig != "" || viper.IsSet("kubeconfig") {
+		viper.Set("kubeconfig", "")
+		color.New(color.FgBlue).Println("Removing local kubeconfig")
+		config.Kubeconfig = ""
+	}
+
 	viper.Set("project", projectID)
 	color.New(color.FgGreen).Printf("Set the current project as %d\n", projectID)
 	err := viper.WriteConfig()
@@ -223,6 +232,12 @@ func (c *CLIConfig) SetProject(projectID uint) error {
 }
 
 func (c *CLIConfig) SetCluster(clusterID uint) error {
+	if config.Kubeconfig != "" || viper.IsSet("kubeconfig") {
+		viper.Set("kubeconfig", "")
+		color.New(color.FgBlue).Println("Removing local kubeconfig")
+		config.Kubeconfig = ""
+	}
+
 	viper.Set("cluster", clusterID)
 	color.New(color.FgGreen).Printf("Set the current cluster as %d\n", clusterID)
 	err := viper.WriteConfig()
@@ -276,3 +291,27 @@ func (c *CLIConfig) SetHelmRepo(helmRepoID uint) error {
 
 	return nil
 }
+
+func (c *CLIConfig) SetKubeconfig(kubeconfig string) error {
+	path, err := filepath.Abs(kubeconfig)
+
+	if err != nil {
+		return err
+	}
+
+	if _, err := os.Stat(path); errors.Is(err, os.ErrNotExist) {
+		return fmt.Errorf("%s does not exist", path)
+	}
+
+	viper.Set("kubeconfig", path)
+	color.New(color.FgGreen).Printf("Set the path to kubeconfig as %s\n", path)
+	err = viper.WriteConfig()
+
+	if err != nil {
+		return err
+	}
+
+	config.Kubeconfig = kubeconfig
+
+	return nil
+}

+ 1 - 1
cli/cmd/portforward.go

@@ -145,7 +145,7 @@ func portForward(user *types.GetAuthenticatedUserResponse, client *api.Client, a
 		pod = pods[0]
 	}
 
-	kubeResp, err := client.GetKubeconfig(context.Background(), cliConf.Project, cliConf.Cluster)
+	kubeResp, err := client.GetKubeconfig(context.Background(), cliConf.Project, cliConf.Cluster, cliConf.Kubeconfig)
 
 	if err != nil {
 		return err

+ 1 - 1
cli/cmd/run.go

@@ -264,7 +264,7 @@ func (p *PorterRunSharedConfig) setSharedConfig() error {
 	pID := cliConf.Project
 	cID := cliConf.Cluster
 
-	kubeResp, err := p.Client.GetKubeconfig(context.TODO(), pID, cID)
+	kubeResp, err := p.Client.GetKubeconfig(context.Background(), pID, cID, cliConf.Kubeconfig)
 
 	if err != nil {
 		return err

+ 1 - 0
dashboard/src/main/Main.tsx

@@ -67,6 +67,7 @@ export default class Main extends Component<PropsType, StateType> {
       .then((res) => {
         this.context.setEdition(res.data?.version);
         this.setState({ local: !res.data?.provisioner });
+        this.context.setEnableGitlab(res.data?.gitlab ? true : false);
       })
       .catch((err) => console.log(err));
   }

+ 48 - 1
dashboard/src/main/home/cluster-dashboard/expanded-chart/jobs/ExpandedJobRun.tsx

@@ -25,12 +25,58 @@ const readableDate = (s: string) => {
   return `${time} on ${date}`;
 };
 
-const renderStatus = (job: any, time: string) => {
+const getLatestPod = (pods: any[]) => {
+  if (!Array.isArray(pods)) {
+    return undefined;
+  }
+
+  return [...pods]
+    .sort((a: any, b: any) => {
+      if (!a?.metadata?.creationTimestamp) {
+        return 1;
+      }
+
+      if (!b?.metadata?.creationTimestamp) {
+        return -1;
+      }
+
+      return (
+        new Date(b?.metadata?.creationTimestamp).getTime() -
+        new Date(a?.metadata?.creationTimestamp).getTime()
+      );
+    })
+    .shift();
+};
+
+const renderStatus = (job: any, pods: any[], time: string) => {
   if (job.status?.succeeded >= 1) {
     return <Status color="#38a88a">Succeeded {time}</Status>;
   }
 
   if (job.status?.failed >= 1) {
+    const appPod = getLatestPod(pods);
+
+    if (appPod) {
+      const appContainerStatus = appPod?.status?.containerStatuses?.find(
+        (container: any) =>
+          container?.state?.terminated?.reason !== "Completed" &&
+          !container?.state?.running
+      );
+
+      if (appContainerStatus) {
+        const reason = appContainerStatus.state.terminated.reason;
+        const exitCode = appContainerStatus.state.terminated.exitCode;
+        const finishTime = appContainerStatus.state.terminated.finishedAt;
+
+        return (
+          <Status color="#cc3d42">
+            Failed at {time ? time : readableDate(finishTime)} - Reason:{" "}
+            {reason} - Exit Code: {exitCode}
+          </Status>
+        );
+      }
+    }
+
     return (
       <Status color="#cc3d42">
         Failed {time}
@@ -163,6 +209,7 @@ const ExpandedJobRun = ({
           <LastDeployed>
             {renderStatus(
               run,
+              pods,
               run.status.completionTime
                 ? readableDate(run.status.completionTime)
                 : ""

+ 12 - 7
dashboard/src/main/home/integrations/Integrations.tsx

@@ -1,4 +1,4 @@
-import React from "react";
+import React, { useContext, useMemo } from "react";
 import { Route, RouteComponentProps, Switch, withRouter } from "react-router";
 
 import { integrationList } from "shared/common";
@@ -9,16 +9,21 @@ import CreateIntegrationForm from "./create-integration/CreateIntegrationForm";
 import IntegrationCategories from "./IntegrationCategories";
 import IntegrationList from "./IntegrationList";
 import TitleSection from "components/TitleSection";
+import { Context } from "shared/Context";
 
 type PropsType = RouteComponentProps;
 
-const IntegrationCategoryStrings = [
-  "registry",
-  "slack",
-  ...(process.env.ENABLE_GITLAB ? ["gitlab"] : []),
-]; /*"kubernetes",*/
-
 const Integrations: React.FC<PropsType> = (props) => {
+  const { enableGitlab } = useContext(Context);
+
+  const IntegrationCategoryStrings = useMemo(() => {
+    if (!enableGitlab) {
+      return ["registry", "slack"];
+    }
+
+    return ["registry", "slack", "gitlab"];
+  }, [enableGitlab]);
+
   return (
     <StyledIntegrations>
       <Switch>

+ 2 - 2
dashboard/src/main/home/launch/launch-flow/SourcePage.tsx

@@ -65,7 +65,7 @@ class SourcePage extends Component<PropsType, StateType> {
     if (sourceType === "") {
       return (
         <BlockList>
-          {capabilities.github && (
+          {capabilities.github || capabilities.gitlab ? (
             <Block onClick={() => setSourceType("repo")}>
               <BlockIcon src="https://git-scm.com/images/logos/downloads/Git-Icon-1788C.png" />
               <BlockTitle>Git Repository</BlockTitle>
@@ -73,7 +73,7 @@ class SourcePage extends Component<PropsType, StateType> {
                 Deploy using source from a Git repo.
               </BlockDescription>
             </Block>
-          )}
+          ) : null}
           <Block onClick={() => setSourceType("registry")}>
             <BlockIcon src="https://cdn4.iconfinder.com/data/icons/logos-and-brands/512/97_Docker_logo_logos-512.png" />
             <BlockTitle>Docker Registry</BlockTitle>

+ 6 - 0
dashboard/src/shared/Context.tsx

@@ -62,6 +62,8 @@ export interface GlobalContextType {
   setHasFinishedOnboarding: (onboardingStatus: boolean) => void;
   canCreateProject: boolean;
   setCanCreateProject: (canCreateProject: boolean) => void;
+  enableGitlab: boolean;
+  setEnableGitlab: (enableGitlab: boolean) => void;
 }
 
 /**
@@ -187,6 +189,10 @@ class ContextProvider extends Component<PropsType, StateType> {
     setCanCreateProject: (canCreateProject: boolean) => {
       this.setState({ canCreateProject });
     },
+    enableGitlab: false,
+    setEnableGitlab: (enableGitlab) => {
+      this.setState({ enableGitlab });
+    },
   };
 
   render() {

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

@@ -346,6 +346,8 @@ export interface ContextProps {
   setHasFinishedOnboarding: (onboardingStatus: boolean) => void;
   canCreateProject: boolean;
   setCanCreateProject: (canCreateProject: boolean) => void;
+  enableGitlab: boolean;
+  setEnableGitlab: (enableGitlab: boolean) => void;
 }
 
 export enum JobStatusType {

+ 1 - 1
internal/integrations/ci/actions/steps.go

@@ -55,7 +55,7 @@ func getCreatePreviewEnvStep(
 			"project": fmt.Sprintf("%d", projectID),
 			"token":   fmt.Sprintf("${{ secrets.%s }}", porterTokenSecretName),
 			"namespace": fmt.Sprintf("pr-${{ github.event.inputs.pr_number }}-%s",
-				strings.ReplaceAll(repoName, "_", "-")),
+				strings.ToLower(strings.ReplaceAll(repoName, "_", "-"))),
 			"pr_id":           "${{ github.event.inputs.pr_number }}",
 			"pr_name":         "${{ github.event.inputs.pr_title }}",
 			"installation_id": fmt.Sprintf("%d", gitInstallationID),