Ver código fonte

remove unused files

Alexander Belanger 4 anos atrás
pai
commit
5cb1ed4614
63 arquivos alterados com 15 adições e 20665 exclusões
  1. 11 11
      cmd/migrate/keyrotate/rotate_test.go
  2. 0 41
      internal/forms/candidate.go
  3. 0 22
      internal/forms/chart.go
  4. 0 517
      internal/forms/cluster.go
  5. 0 1053
      internal/forms/cluster_test.go
  6. 0 8
      internal/forms/domain.go
  7. 0 48
      internal/forms/git_action.go
  8. 0 29
      internal/forms/helm_repo.go
  9. 0 362
      internal/forms/helper_test.go
  10. 0 186
      internal/forms/infra.go
  11. 0 77
      internal/forms/integration.go
  12. 0 30
      internal/forms/invite.go
  13. 0 56
      internal/forms/k8s.go
  14. 0 12
      internal/forms/metrics.go
  15. 0 30
      internal/forms/project.go
  16. 0 85
      internal/forms/registry.go
  17. 0 139
      internal/forms/release.go
  18. 0 120
      internal/forms/user.go
  19. 4 0
      internal/repository/test/auth.go
  20. 0 382
      server/api/api.go
  21. 0 15
      server/api/capability_handler.go
  22. 0 456
      server/api/cluster_handler.go
  23. 0 480
      server/api/cluster_handler_test.go
  24. 0 470
      server/api/deploy_handler.go
  25. 0 113
      server/api/deploy_handler_test.go
  26. 0 91
      server/api/dns_record_handler.go
  27. 0 168
      server/api/errors.go
  28. 0 265
      server/api/git_action_handler.go
  29. 0 531
      server/api/git_repo_handler.go
  30. 0 113
      server/api/git_repo_handler_test.go
  31. 0 44
      server/api/health_handler.go
  32. 0 131
      server/api/helm_repo_handler.go
  33. 0 157
      server/api/helm_repo_handler_test.go
  34. 0 156
      server/api/helpers_test.go
  35. 0 41
      server/api/infra_handler.go
  36. 0 626
      server/api/integration_handler.go
  37. 0 319
      server/api/integration_handler_test.go
  38. 0 298
      server/api/invite_handler.go
  39. 0 297
      server/api/invite_handler_test.go
  40. 0 1529
      server/api/k8s_handler.go
  41. 0 128
      server/api/k8s_handler_test.go
  42. 0 140
      server/api/notifications_handler.go
  43. 0 102
      server/api/oauth_do_handler.go
  44. 0 370
      server/api/oauth_github_handler.go
  45. 0 202
      server/api/oauth_google_handler.go
  46. 0 191
      server/api/oauth_slack_handler.go
  47. 0 348
      server/api/project_handler.go
  48. 0 184
      server/api/project_handler_test.go
  49. 0 1146
      server/api/provision_handler.go
  50. 0 561
      server/api/registry_handler.go
  51. 0 314
      server/api/registry_handler_test.go
  52. 0 1844
      server/api/release_handler.go
  53. 0 507
      server/api/release_handler_test.go
  54. 0 166
      server/api/template_handler.go
  55. 0 123
      server/api/template_handler_test.go
  56. 0 901
      server/api/user_handler.go
  57. 0 575
      server/api/user_handler_test.go
  58. 0 34
      server/api/welcome_handler.go
  59. 0 1262
      server/middleware/auth.go
  60. 0 11
      server/middleware/json.go
  61. 0 76
      server/middleware/requestlog/handler.go
  62. 0 110
      server/middleware/requestlog/log_entry.go
  63. 0 1862
      server/router/router.go

+ 11 - 11
cmd/migrate/keyrotate/rotate_test.go

@@ -45,7 +45,7 @@ func TestClusterModelRotation(t *testing.T) {
 
 	// decrypt with the old key
 	for _, c := range clusters {
-		cluster, err := repo.ReadCluster(c.ID)
+		cluster, err := repo.ReadCluster(c.ProjectID, c.ID)
 
 		if err != nil {
 			t.Fatalf("error reading cluster: %v\n", err)
@@ -97,7 +97,7 @@ func TestClusterCandidateModelRotation(t *testing.T) {
 
 	// decrypt with the old key
 	for _, c := range ccs {
-		cc, err := repo.ReadClusterCandidate(c.ID)
+		cc, err := repo.ReadClusterCandidate(c.ProjectID, c.ID)
 
 		if err != nil {
 			t.Fatalf("error reading cluster: %v\n", err)
@@ -149,7 +149,7 @@ func TestRegistryModelRotation(t *testing.T) {
 
 	// decrypt with the old key
 	for _, r := range regs {
-		registry, err := repo.ReadRegistry(r.ID)
+		registry, err := repo.ReadRegistry(r.ProjectID, r.ID)
 
 		if err != nil {
 			t.Fatalf("error reading registry: %v\n", err)
@@ -197,7 +197,7 @@ func TestHelmRepoModelRotation(t *testing.T) {
 
 	// decrypt with the old key
 	for _, h := range hrs {
-		hr, err := repo.ReadHelmRepo(h.ID)
+		hr, err := repo.ReadHelmRepo(h.ProjectID, h.ID)
 
 		if err != nil {
 			t.Fatalf("error reading helm repo: %v\n", err)
@@ -245,7 +245,7 @@ func TestInfraModelRotation(t *testing.T) {
 
 	// decrypt with the old key
 	for _, i := range infras {
-		infra, err := repo.ReadInfra(i.ID)
+		infra, err := repo.ReadInfra(i.ProjectID, i.ID)
 
 		if err != nil {
 			t.Fatalf("error reading infra: %v\n", err)
@@ -293,7 +293,7 @@ func TestKubeIntegrationModelRotation(t *testing.T) {
 
 	// decrypt with the old key
 	for _, k := range kis {
-		ki, err := repo.ReadKubeIntegration(k.ID)
+		ki, err := repo.ReadKubeIntegration(k.ProjectID, k.ID)
 
 		if err != nil {
 			t.Fatalf("error reading infra: %v\n", err)
@@ -361,7 +361,7 @@ func TestBasicIntegrationModelRotation(t *testing.T) {
 
 	// decrypt with the old key
 	for _, k := range basics {
-		basic, err := repo.ReadBasicIntegration(k.ID)
+		basic, err := repo.ReadBasicIntegration(k.ProjectID, k.ID)
 
 		if err != nil {
 			t.Fatalf("error reading infra: %v\n", err)
@@ -413,7 +413,7 @@ func TestOIDCIntegrationModelRotation(t *testing.T) {
 
 	// decrypt with the old key
 	for _, k := range oidcs {
-		oidc, err := repo.ReadOIDCIntegration(k.ID)
+		oidc, err := repo.ReadOIDCIntegration(k.ProjectID, k.ID)
 
 		if err != nil {
 			t.Fatalf("error reading infra: %v\n", err)
@@ -481,7 +481,7 @@ func TestOAuthIntegrationModelRotation(t *testing.T) {
 
 	// decrypt with the old key
 	for _, k := range oauths {
-		oauth, err := repo.ReadOAuthIntegration(k.ID)
+		oauth, err := repo.ReadOAuthIntegration(k.ProjectID, k.ID)
 
 		if err != nil {
 			t.Fatalf("error reading infra: %v\n", err)
@@ -537,7 +537,7 @@ func TestGCPIntegrationModelRotation(t *testing.T) {
 
 	// decrypt with the old key
 	for _, k := range gcps {
-		gcp, err := repo.ReadGCPIntegration(k.ID)
+		gcp, err := repo.ReadGCPIntegration(k.ProjectID, k.ID)
 
 		if err != nil {
 			t.Fatalf("error reading infra: %v\n", err)
@@ -585,7 +585,7 @@ func TestAWSIntegrationModelRotation(t *testing.T) {
 
 	// decrypt with the old key
 	for _, k := range awss {
-		aws, err := repo.ReadAWSIntegration(k.ID)
+		aws, err := repo.ReadAWSIntegration(k.ProjectID, k.ID)
 
 		if err != nil {
 			t.Fatalf("error reading infra: %v\n", err)

+ 0 - 41
internal/forms/candidate.go

@@ -1,41 +0,0 @@
-package forms
-
-import (
-	"github.com/porter-dev/porter/internal/kubernetes"
-	"github.com/porter-dev/porter/internal/models"
-)
-
-// CreateClusterCandidatesForm represents the accepted values for
-// creating a list of ClusterCandidates from a kubeconfig
-type CreateClusterCandidatesForm struct {
-	ProjectID  uint   `json:"project_id"`
-	Kubeconfig string `json:"kubeconfig"`
-
-	// Represents whether the auth mechanism should be designated as
-	// "local": if so, the auth mechanism uses local plugins/mechanisms purely from the
-	// kubeconfig.
-	IsLocal bool `json:"is_local"`
-}
-
-// ToClusterCandidates creates a ClusterCandidate from the kubeconfig and
-// project id
-func (csa *CreateClusterCandidatesForm) ToClusterCandidates(
-	isServerLocal bool,
-) ([]*models.ClusterCandidate, error) {
-	candidates, err := kubernetes.GetClusterCandidatesFromKubeconfig(
-		[]byte(csa.Kubeconfig),
-		csa.ProjectID,
-		// can only use "local" auth mechanism if the server is running locally
-		isServerLocal && csa.IsLocal,
-	)
-
-	if err != nil {
-		return nil, err
-	}
-
-	for _, cc := range candidates {
-		cc.ProjectID = csa.ProjectID
-	}
-
-	return candidates, nil
-}

+ 0 - 22
internal/forms/chart.go

@@ -1,22 +0,0 @@
-package forms
-
-import "net/url"
-
-// ChartForm is the base type for CRUD operations on charts
-type ChartForm struct {
-	RepoURL string 
-	Name    string `json:"name"`
-	Version string `json:"version"`
-}
-
-// PopulateRepoURLFromQueryParams populates the repo url in the ChartForm using the passed
-// url.Values (the parsed query params)
-func (cf *ChartForm) PopulateRepoURLFromQueryParams(
-	vals url.Values,
-) error {
-	if repoURL, ok := vals["repo_url"]; ok && len(repoURL) == 1 {
-		cf.RepoURL = repoURL[0]
-	}
-
-	return nil
-}

+ 0 - 517
internal/forms/cluster.go

@@ -1,517 +0,0 @@
-package forms
-
-import (
-	"encoding/base64"
-	"errors"
-	"fmt"
-	"net/url"
-	"regexp"
-	"strings"
-
-	"github.com/porter-dev/porter/internal/kubernetes"
-	"github.com/porter-dev/porter/internal/models"
-	"github.com/porter-dev/porter/internal/repository"
-	"k8s.io/client-go/tools/clientcmd/api"
-
-	ints "github.com/porter-dev/porter/internal/models/integrations"
-)
-
-// CreateClusterForm represents the accepted values for creating a
-// cluster through manual configuration (not through a kubeconfig)
-type CreateClusterForm struct {
-	Name      string `json:"name" form:"required"`
-	ProjectID uint   `json:"project_id" form:"required"`
-	Server    string `json:"server" form:"required"`
-
-	GCPIntegrationID uint `json:"gcp_integration_id"`
-	AWSIntegrationID uint `json:"aws_integration_id"`
-
-	CertificateAuthorityData string `json:"certificate_authority_data,omitempty"`
-}
-
-// ToCluster converts the form to a cluster
-func (ccf *CreateClusterForm) ToCluster() (*models.Cluster, error) {
-	var authMechanism models.ClusterAuth
-
-	if ccf.GCPIntegrationID != 0 {
-		authMechanism = models.GCP
-	} else if ccf.AWSIntegrationID != 0 {
-		authMechanism = models.AWS
-	} else {
-		return nil, fmt.Errorf("must include aws or gcp integration id")
-	}
-
-	cert := make([]byte, 0)
-
-	if ccf.CertificateAuthorityData != "" {
-		// determine if data is base64 decoded using regex
-		re := regexp.MustCompile(`^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)?$`)
-
-		// if it matches the base64 regex, decode it
-		if re.MatchString(ccf.CertificateAuthorityData) {
-			decoded, err := base64.StdEncoding.DecodeString(ccf.CertificateAuthorityData)
-
-			if err != nil {
-				return nil, err
-			}
-
-			cert = []byte(decoded)
-		}
-	}
-
-	return &models.Cluster{
-		ProjectID:                ccf.ProjectID,
-		AuthMechanism:            authMechanism,
-		Name:                     ccf.Name,
-		Server:                   ccf.Server,
-		GCPIntegrationID:         ccf.GCPIntegrationID,
-		AWSIntegrationID:         ccf.AWSIntegrationID,
-		CertificateAuthorityData: cert,
-	}, nil
-}
-
-// UpdateClusterForm represents the accepted values for updating a
-// cluster (only name for now)
-type UpdateClusterForm struct {
-	ID uint
-
-	Name string `json:"name" form:"required"`
-}
-
-// ToCluster converts the form to a cluster
-func (ucf *UpdateClusterForm) ToCluster(repo repository.ClusterRepository) (*models.Cluster, error) {
-	cluster, err := repo.ReadCluster(ucf.ID)
-
-	if err != nil {
-		return nil, err
-	}
-
-	cluster.Name = ucf.Name
-
-	return cluster, nil
-}
-
-// ResolveClusterForm will resolve a cluster candidate and create a new cluster
-type ResolveClusterForm struct {
-	Resolver *models.ClusterResolverAll `form:"required"`
-
-	ClusterCandidateID uint `json:"cluster_candidate_id" form:"required"`
-	ProjectID          uint `json:"project_id" form:"required"`
-	UserID             uint `json:"user_id" form:"required"`
-
-	// populated during the ResolveIntegration step
-	IntegrationID    uint
-	ClusterCandidate *models.ClusterCandidate
-	RawConf          *api.Config
-}
-
-// ResolveIntegration creates an integration in the DB
-func (rcf *ResolveClusterForm) ResolveIntegration(
-	repo repository.Repository,
-) error {
-	cc, err := repo.Cluster().ReadClusterCandidate(rcf.ClusterCandidateID)
-
-	if err != nil {
-		return err
-	}
-
-	rcf.ClusterCandidate = cc
-
-	rawConf, err := kubernetes.GetRawConfigFromBytes(cc.Kubeconfig)
-
-	if err != nil {
-		return err
-	}
-
-	rcf.RawConf = rawConf
-
-	context := rawConf.Contexts[rawConf.CurrentContext]
-
-	authInfoName := context.AuthInfo
-	authInfo := rawConf.AuthInfos[authInfoName]
-
-	// iterate through the resolvers, and use the ClusterResolverAll to populate
-	// the required fields
-	var id uint
-
-	switch cc.AuthMechanism {
-	case models.X509:
-		id, err = rcf.resolveX509(repo, authInfo)
-	case models.Bearer:
-		id, err = rcf.resolveToken(repo, authInfo)
-	case models.Basic:
-		id, err = rcf.resolveBasic(repo, authInfo)
-	case models.Local:
-		id, err = rcf.resolveLocal(repo, authInfo)
-	case models.OIDC:
-		id, err = rcf.resolveOIDC(repo, authInfo)
-	case models.GCP:
-		id, err = rcf.resolveGCP(repo, authInfo)
-	case models.AWS:
-		id, err = rcf.resolveAWS(repo, authInfo)
-	}
-
-	if err != nil {
-		return err
-	}
-
-	rcf.IntegrationID = id
-
-	return nil
-}
-
-func (rcf *ResolveClusterForm) resolveX509(
-	repo repository.Repository,
-	authInfo *api.AuthInfo,
-) (uint, error) {
-	ki := &ints.KubeIntegration{
-		Mechanism: ints.KubeX509,
-		UserID:    rcf.UserID,
-		ProjectID: rcf.ProjectID,
-	}
-
-	// attempt to construct cert and key from raw config
-	if len(authInfo.ClientCertificateData) > 0 {
-		ki.ClientCertificateData = authInfo.ClientCertificateData
-	}
-
-	if len(authInfo.ClientKeyData) > 0 {
-		ki.ClientKeyData = authInfo.ClientKeyData
-	}
-
-	// override with resolver
-	if rcf.Resolver.ClientCertData != "" {
-		decoded, err := base64.StdEncoding.DecodeString(rcf.Resolver.ClientCertData)
-
-		if err != nil {
-			return 0, err
-		}
-
-		ki.ClientCertificateData = decoded
-	}
-
-	if rcf.Resolver.ClientKeyData != "" {
-		decoded, err := base64.StdEncoding.DecodeString(rcf.Resolver.ClientKeyData)
-
-		if err != nil {
-			return 0, err
-		}
-
-		ki.ClientKeyData = decoded
-	}
-
-	// if resolvable, write kube integration to repo
-	if len(ki.ClientCertificateData) == 0 || len(ki.ClientKeyData) == 0 {
-		return 0, errors.New("could not resolve kube integration (x509)")
-	}
-
-	// return integration id if exists
-	ki, err := repo.KubeIntegration().CreateKubeIntegration(ki)
-
-	if err != nil {
-		return 0, err
-	}
-
-	return ki.Model.ID, nil
-}
-
-func (rcf *ResolveClusterForm) resolveToken(
-	repo repository.Repository,
-	authInfo *api.AuthInfo,
-) (uint, error) {
-	ki := &ints.KubeIntegration{
-		Mechanism: ints.KubeBearer,
-		UserID:    rcf.UserID,
-		ProjectID: rcf.ProjectID,
-	}
-
-	// attempt to construct token from raw config
-	if len(authInfo.Token) > 0 {
-		ki.Token = []byte(authInfo.Token)
-	}
-
-	// supplement with resolver
-	if rcf.Resolver.TokenData != "" {
-		ki.Token = []byte(rcf.Resolver.TokenData)
-	}
-
-	// if resolvable, write kube integration to repo
-	if len(ki.Token) == 0 {
-		return 0, errors.New("could not resolve kube integration (token)")
-	}
-
-	// return integration id if exists
-	ki, err := repo.KubeIntegration().CreateKubeIntegration(ki)
-
-	if err != nil {
-		return 0, err
-	}
-
-	return ki.Model.ID, nil
-}
-
-func (rcf *ResolveClusterForm) resolveBasic(
-	repo repository.Repository,
-	authInfo *api.AuthInfo,
-) (uint, error) {
-	ki := &ints.KubeIntegration{
-		Mechanism: ints.KubeBasic,
-		UserID:    rcf.UserID,
-		ProjectID: rcf.ProjectID,
-	}
-
-	if len(authInfo.Username) > 0 {
-		ki.Username = []byte(authInfo.Username)
-	}
-
-	if len(authInfo.Password) > 0 {
-		ki.Password = []byte(authInfo.Password)
-	}
-
-	if len(ki.Username) == 0 || len(ki.Password) == 0 {
-		return 0, errors.New("could not resolve kube integration (basic)")
-	}
-
-	// return integration id if exists
-	ki, err := repo.KubeIntegration().CreateKubeIntegration(ki)
-
-	if err != nil {
-		return 0, err
-	}
-
-	return ki.Model.ID, nil
-}
-
-func (rcf *ResolveClusterForm) resolveLocal(
-	repo repository.Repository,
-	authInfo *api.AuthInfo,
-) (uint, error) {
-	ki := &ints.KubeIntegration{
-		Mechanism:  ints.KubeLocal,
-		UserID:     rcf.UserID,
-		ProjectID:  rcf.ProjectID,
-		Kubeconfig: rcf.ClusterCandidate.Kubeconfig,
-	}
-
-	// return integration id if exists
-	ki, err := repo.KubeIntegration().CreateKubeIntegration(ki)
-
-	if err != nil {
-		return 0, err
-	}
-
-	return ki.Model.ID, nil
-}
-
-func (rcf *ResolveClusterForm) resolveOIDC(
-	repo repository.Repository,
-	authInfo *api.AuthInfo,
-) (uint, error) {
-	oidc := &ints.OIDCIntegration{
-		Client:    ints.OIDCKube,
-		UserID:    rcf.UserID,
-		ProjectID: rcf.ProjectID,
-	}
-
-	if url, ok := authInfo.AuthProvider.Config["idp-issuer-url"]; ok {
-		oidc.IssuerURL = []byte(url)
-	}
-
-	if clientID, ok := authInfo.AuthProvider.Config["client-id"]; ok {
-		oidc.ClientID = []byte(clientID)
-	}
-
-	if clientSecret, ok := authInfo.AuthProvider.Config["client-secret"]; ok {
-		oidc.ClientSecret = []byte(clientSecret)
-	}
-
-	if caData, ok := authInfo.AuthProvider.Config["idp-certificate-authority-data"]; ok {
-		// based on the implementation, the oidc plugin expects the data to be base64 encoded,
-		// which means we will not decode it here
-		// reference: https://github.com/kubernetes/kubernetes/blob/9dfb4c876bfca7a5ae84259fae2bc337ed90c2d7/staging/src/k8s.io/client-go/plugin/pkg/client/auth/oidc/oidc.go#L135
-		oidc.CertificateAuthorityData = []byte(caData)
-	}
-
-	if idToken, ok := authInfo.AuthProvider.Config["id-token"]; ok {
-		oidc.IDToken = []byte(idToken)
-	}
-
-	if refreshToken, ok := authInfo.AuthProvider.Config["refresh-token"]; ok {
-		oidc.RefreshToken = []byte(refreshToken)
-	}
-
-	// override with resolver
-	if rcf.Resolver.OIDCIssuerCAData != "" {
-		// based on the implementation, the oidc plugin expects the data to be base64 encoded,
-		// which means we will not decode it here
-		// reference: https://github.com/kubernetes/kubernetes/blob/9dfb4c876bfca7a5ae84259fae2bc337ed90c2d7/staging/src/k8s.io/client-go/plugin/pkg/client/auth/oidc/oidc.go#L135
-		oidc.CertificateAuthorityData = []byte(rcf.Resolver.OIDCIssuerCAData)
-	}
-
-	// return integration id if exists
-	oidc, err := repo.OIDCIntegration().CreateOIDCIntegration(oidc)
-
-	if err != nil {
-		return 0, err
-	}
-
-	return oidc.Model.ID, nil
-}
-
-func (rcf *ResolveClusterForm) resolveGCP(
-	repo repository.Repository,
-	authInfo *api.AuthInfo,
-) (uint, error) {
-	// TODO -- add GCP project ID and GCP email so that source is trackable
-	gcp := &ints.GCPIntegration{
-		UserID:    rcf.UserID,
-		ProjectID: rcf.ProjectID,
-	}
-
-	// supplement with resolver
-	if rcf.Resolver.GCPKeyData != "" {
-		gcp.GCPKeyData = []byte(rcf.Resolver.GCPKeyData)
-	}
-
-	// throw error if no data
-	if len(gcp.GCPKeyData) == 0 {
-		return 0, errors.New("could not resolve gcp integration")
-	}
-
-	// return integration id if exists
-	gcp, err := repo.GCPIntegration().CreateGCPIntegration(gcp)
-
-	if err != nil {
-		return 0, err
-	}
-
-	return gcp.Model.ID, nil
-}
-
-func (rcf *ResolveClusterForm) resolveAWS(
-	repo repository.Repository,
-	authInfo *api.AuthInfo,
-) (uint, error) {
-	// TODO -- add AWS session token as an optional param
-	// TODO -- add AWS entity and user ARN
-	aws := &ints.AWSIntegration{
-		UserID:    rcf.UserID,
-		ProjectID: rcf.ProjectID,
-	}
-
-	// override with resolver
-	if rcf.Resolver.AWSClusterID != "" {
-		aws.AWSClusterID = []byte(rcf.Resolver.AWSClusterID)
-	}
-
-	if rcf.Resolver.AWSAccessKeyID != "" {
-		aws.AWSAccessKeyID = []byte(rcf.Resolver.AWSAccessKeyID)
-	}
-
-	if rcf.Resolver.AWSSecretAccessKey != "" {
-		aws.AWSSecretAccessKey = []byte(rcf.Resolver.AWSSecretAccessKey)
-	}
-
-	// throw error if no data
-	if len(aws.AWSClusterID) == 0 || len(aws.AWSAccessKeyID) == 0 || len(aws.AWSSecretAccessKey) == 0 {
-		return 0, errors.New("could not resolve aws integration")
-	}
-
-	// return integration id if exists
-	aws, err := repo.AWSIntegration().CreateAWSIntegration(aws)
-
-	if err != nil {
-		return 0, err
-	}
-
-	return aws.Model.ID, nil
-}
-
-// ResolveCluster writes a new cluster to the DB -- this must be called after
-// rcf.ResolveIntegration, since it relies on the previously created integration.
-func (rcf *ResolveClusterForm) ResolveCluster(
-	repo repository.Repository,
-) (*models.Cluster, error) {
-	// build a cluster from the candidate
-	cluster, err := rcf.buildCluster()
-
-	if err != nil {
-		return nil, err
-	}
-
-	// save cluster to db
-	return repo.Cluster().CreateCluster(cluster)
-}
-
-func (rcf *ResolveClusterForm) buildCluster() (*models.Cluster, error) {
-	rawConf := rcf.RawConf
-
-	kcContext := rawConf.Contexts[rawConf.CurrentContext]
-
-	kcAuthInfoName := kcContext.AuthInfo
-	kcAuthInfo := rawConf.AuthInfos[kcAuthInfoName]
-
-	kcClusterName := kcContext.Cluster
-	kcCluster := rawConf.Clusters[kcClusterName]
-
-	cc := rcf.ClusterCandidate
-
-	cluster := &models.Cluster{
-		AuthMechanism:           cc.AuthMechanism,
-		ProjectID:               cc.ProjectID,
-		Name:                    cc.Name,
-		Server:                  cc.Server,
-		ClusterLocationOfOrigin: kcCluster.LocationOfOrigin,
-		TLSServerName:           kcCluster.TLSServerName,
-		InsecureSkipTLSVerify:   kcCluster.InsecureSkipTLSVerify,
-		UserLocationOfOrigin:    kcAuthInfo.LocationOfOrigin,
-		UserImpersonate:         kcAuthInfo.Impersonate,
-	}
-
-	if len(kcAuthInfo.ImpersonateGroups) > 0 {
-		cluster.UserImpersonateGroups = strings.Join(kcAuthInfo.ImpersonateGroups, ",")
-	}
-
-	if len(kcCluster.CertificateAuthorityData) > 0 {
-		cluster.CertificateAuthorityData = kcCluster.CertificateAuthorityData
-	}
-
-	if rcf.Resolver.ClusterCAData != "" {
-		decoded, err := base64.StdEncoding.DecodeString(rcf.Resolver.ClusterCAData)
-
-		// skip if decoding error
-		if err != nil {
-			return nil, err
-		}
-
-		cluster.CertificateAuthorityData = decoded
-	}
-
-	if rcf.Resolver.ClusterHostname != "" {
-		serverURL, err := url.Parse(cluster.Server)
-		if err != nil {
-			return nil, err
-		}
-
-		if serverURL.Port() == "" {
-			serverURL.Host = rcf.Resolver.ClusterHostname
-		} else {
-			serverURL.Host = rcf.Resolver.ClusterHostname + ":" + serverURL.Port()
-		}
-
-		cluster.Server = serverURL.String()
-	}
-
-	switch cc.AuthMechanism {
-	case models.X509, models.Bearer, models.Basic, models.Local:
-		cluster.KubeIntegrationID = rcf.IntegrationID
-	case models.OIDC:
-		cluster.OIDCIntegrationID = rcf.IntegrationID
-	case models.GCP:
-		cluster.GCPIntegrationID = rcf.IntegrationID
-	case models.AWS:
-		cluster.AWSIntegrationID = rcf.IntegrationID
-	}
-
-	return cluster, nil
-}

+ 0 - 1053
internal/forms/cluster_test.go

@@ -1,1053 +0,0 @@
-package forms_test
-
-import (
-	"testing"
-
-	"github.com/go-test/deep"
-	"github.com/porter-dev/porter/internal/forms"
-	"github.com/porter-dev/porter/internal/kubernetes/fixtures"
-	"github.com/porter-dev/porter/internal/models"
-	"gorm.io/gorm"
-	"k8s.io/client-go/tools/clientcmd"
-
-	ints "github.com/porter-dev/porter/internal/models/integrations"
-)
-
-type clusterTest struct {
-	name    string
-	raw     string
-	isLocal bool
-
-	resolver       *models.ClusterResolverAll
-	expIntegration interface{}
-	expCluster     *models.Cluster
-}
-
-var ClusterTests = []clusterTest{
-	clusterTest{
-		name:     "local test should preserve kubeconfig",
-		raw:      fixtures.ClusterCAWithData,
-		isLocal:  true,
-		resolver: &models.ClusterResolverAll{},
-		expIntegration: &ints.KubeIntegration{
-			Mechanism:  ints.KubeLocal,
-			UserID:     1,
-			ProjectID:  1,
-			Kubeconfig: []byte(fixtures.ClusterCAWithData),
-		},
-		expCluster: &models.Cluster{
-			AuthMechanism:            models.Local,
-			ProjectID:                1,
-			Name:                     "cluster-test",
-			Server:                   "https://10.10.10.10",
-			KubeIntegrationID:        1,
-			CertificateAuthorityData: []byte("-----BEGIN CER"),
-		},
-	},
-	clusterTest{
-		name:     "cluster with data",
-		raw:      fixtures.ClusterCAWithData,
-		isLocal:  false,
-		resolver: &models.ClusterResolverAll{},
-		expIntegration: &ints.KubeIntegration{
-			Mechanism:             ints.KubeX509,
-			UserID:                1,
-			ProjectID:             1,
-			ClientCertificateData: []byte("-----BEGIN CER"),
-			ClientKeyData:         []byte("-----BEGIN CER"),
-		},
-		expCluster: &models.Cluster{
-			AuthMechanism:            models.X509,
-			ProjectID:                1,
-			Name:                     "cluster-test",
-			Server:                   "https://10.10.10.10",
-			KubeIntegrationID:        2,
-			CertificateAuthorityData: []byte("-----BEGIN CER"),
-		},
-	},
-	clusterTest{
-		name:    "cluster without data",
-		raw:     fixtures.ClusterCAWithoutData,
-		isLocal: false,
-		resolver: &models.ClusterResolverAll{
-			ClusterCAData: "LS0tLS1CRUdJTiBDRVJ=",
-		},
-		expIntegration: &ints.KubeIntegration{
-			Mechanism:             ints.KubeX509,
-			UserID:                1,
-			ProjectID:             1,
-			ClientCertificateData: []byte("-----BEGIN CER"),
-			ClientKeyData:         []byte("-----BEGIN CER"),
-		},
-		expCluster: &models.Cluster{
-			AuthMechanism:            models.X509,
-			ProjectID:                1,
-			Name:                     "cluster-test",
-			Server:                   "https://10.10.10.10",
-			KubeIntegrationID:        3,
-			CertificateAuthorityData: []byte("-----BEGIN CER"),
-		},
-	},
-	clusterTest{
-		name:    "cluster localhost",
-		raw:     fixtures.ClusterLocalhost,
-		isLocal: false,
-		resolver: &models.ClusterResolverAll{
-			ClusterHostname: "example.com",
-		},
-		expIntegration: &ints.KubeIntegration{
-			Mechanism:             ints.KubeX509,
-			UserID:                1,
-			ProjectID:             1,
-			ClientCertificateData: []byte("-----BEGIN CER"),
-			ClientKeyData:         []byte("-----BEGIN CER"),
-		},
-		expCluster: &models.Cluster{
-			AuthMechanism:     models.X509,
-			ProjectID:         1,
-			Name:              "cluster-test",
-			Server:            "https://example.com:30000",
-			KubeIntegrationID: 4,
-		},
-	},
-	clusterTest{
-		name:     "x509 cert and key data",
-		raw:      fixtures.X509WithData,
-		isLocal:  false,
-		resolver: &models.ClusterResolverAll{},
-		expIntegration: &ints.KubeIntegration{
-			Mechanism:             ints.KubeX509,
-			UserID:                1,
-			ProjectID:             1,
-			ClientCertificateData: []byte("-----BEGIN CER"),
-			ClientKeyData:         []byte("-----BEGIN CER"),
-		},
-		expCluster: &models.Cluster{
-			AuthMechanism:     models.X509,
-			ProjectID:         1,
-			Name:              "cluster-test",
-			Server:            "https://10.10.10.10",
-			KubeIntegrationID: 5,
-		},
-	},
-	clusterTest{
-		name:    "x509 no cert data",
-		raw:     fixtures.X509WithoutCertData,
-		isLocal: false,
-		resolver: &models.ClusterResolverAll{
-			ClientCertData: "LS0tLS1CRUdJTiBDRVJ=",
-		},
-		expIntegration: &ints.KubeIntegration{
-			Mechanism:             ints.KubeX509,
-			UserID:                1,
-			ProjectID:             1,
-			ClientCertificateData: []byte("-----BEGIN CER"),
-			ClientKeyData:         []byte("-----BEGIN CER"),
-		},
-		expCluster: &models.Cluster{
-			AuthMechanism:     models.X509,
-			ProjectID:         1,
-			Name:              "cluster-test",
-			Server:            "https://10.10.10.10",
-			KubeIntegrationID: 6,
-		},
-	},
-	clusterTest{
-		name:    "x509 no key data",
-		raw:     fixtures.X509WithoutKeyData,
-		isLocal: false,
-		resolver: &models.ClusterResolverAll{
-			ClientKeyData: "LS0tLS1CRUdJTiBDRVJ=",
-		},
-		expIntegration: &ints.KubeIntegration{
-			Mechanism:             ints.KubeX509,
-			UserID:                1,
-			ProjectID:             1,
-			ClientCertificateData: []byte("-----BEGIN CER"),
-			ClientKeyData:         []byte("-----BEGIN CER"),
-		},
-		expCluster: &models.Cluster{
-			AuthMechanism:     models.X509,
-			ProjectID:         1,
-			Name:              "cluster-test",
-			Server:            "https://10.10.10.10",
-			KubeIntegrationID: 7,
-		},
-	},
-	clusterTest{
-		name:    "x509 no cert and key data",
-		raw:     fixtures.X509WithoutCertAndKeyData,
-		isLocal: false,
-		resolver: &models.ClusterResolverAll{
-			ClientCertData: "LS0tLS1CRUdJTiBDRVJ=",
-			ClientKeyData:  "LS0tLS1CRUdJTiBDRVJ=",
-		},
-		expIntegration: &ints.KubeIntegration{
-			Mechanism:             ints.KubeX509,
-			UserID:                1,
-			ProjectID:             1,
-			ClientCertificateData: []byte("-----BEGIN CER"),
-			ClientKeyData:         []byte("-----BEGIN CER"),
-		},
-		expCluster: &models.Cluster{
-			AuthMechanism:     models.X509,
-			ProjectID:         1,
-			Name:              "cluster-test",
-			Server:            "https://10.10.10.10",
-			KubeIntegrationID: 8,
-		},
-	},
-	clusterTest{
-		name:     "bearer token with data",
-		raw:      fixtures.BearerTokenWithData,
-		isLocal:  false,
-		resolver: &models.ClusterResolverAll{},
-		expIntegration: &ints.KubeIntegration{
-			Mechanism: ints.KubeBearer,
-			UserID:    1,
-			ProjectID: 1,
-			Token:     []byte("LS0tLS1CRUdJTiBDRVJ="),
-		},
-		expCluster: &models.Cluster{
-			AuthMechanism:     models.Bearer,
-			ProjectID:         1,
-			Name:              "cluster-test",
-			Server:            "https://10.10.10.10",
-			KubeIntegrationID: 9,
-		},
-	},
-	clusterTest{
-		name:    "bearer token without data",
-		raw:     fixtures.BearerTokenWithoutData,
-		isLocal: false,
-		resolver: &models.ClusterResolverAll{
-			TokenData: "tokentoken",
-		},
-		expIntegration: &ints.KubeIntegration{
-			Mechanism: ints.KubeBearer,
-			UserID:    1,
-			ProjectID: 1,
-			Token:     []byte("tokentoken"),
-		},
-		expCluster: &models.Cluster{
-			AuthMechanism:     models.Bearer,
-			ProjectID:         1,
-			Name:              "cluster-test",
-			Server:            "https://10.10.10.10",
-			KubeIntegrationID: 10,
-		},
-	},
-	clusterTest{
-		name:     "basic auth",
-		raw:      fixtures.BasicAuth,
-		isLocal:  false,
-		resolver: &models.ClusterResolverAll{},
-		expIntegration: &ints.KubeIntegration{
-			Mechanism: ints.KubeBasic,
-			UserID:    1,
-			ProjectID: 1,
-			Username:  []byte("admin"),
-			Password:  []byte("changeme"),
-		},
-		expCluster: &models.Cluster{
-			AuthMechanism:            models.Basic,
-			ProjectID:                1,
-			Name:                     "cluster-test",
-			Server:                   "https://10.10.10.10",
-			KubeIntegrationID:        11,
-			CertificateAuthorityData: []byte("-----BEGIN CER"),
-		},
-	},
-	clusterTest{
-		name:    "gcp plugin",
-		raw:     fixtures.GCPPlugin,
-		isLocal: false,
-		resolver: &models.ClusterResolverAll{
-			GCPKeyData: `{"key":"data"}`,
-		},
-		expIntegration: &ints.GCPIntegration{
-			UserID:     1,
-			ProjectID:  1,
-			GCPKeyData: []byte(`{"key":"data"}`),
-		},
-		expCluster: &models.Cluster{
-			AuthMechanism:            models.GCP,
-			ProjectID:                1,
-			Name:                     "cluster-test",
-			Server:                   "https://10.10.10.10",
-			GCPIntegrationID:         1,
-			CertificateAuthorityData: []byte("-----BEGIN CER"),
-		},
-	},
-	clusterTest{
-		name:    "aws iam authenticator",
-		raw:     fixtures.AWSIamAuthenticatorExec,
-		isLocal: false,
-		resolver: &models.ClusterResolverAll{
-			AWSAccessKeyID:     "accesskey",
-			AWSClusterID:       "cluster-test-aws-id-guess",
-			AWSSecretAccessKey: "secret",
-		},
-		expIntegration: &ints.AWSIntegration{
-			UserID:             1,
-			ProjectID:          1,
-			AWSAccessKeyID:     []byte("accesskey"),
-			AWSClusterID:       []byte("cluster-test-aws-id-guess"),
-			AWSSecretAccessKey: []byte("secret"),
-		},
-		expCluster: &models.Cluster{
-			AuthMechanism:            models.AWS,
-			ProjectID:                1,
-			Name:                     "cluster-test",
-			Server:                   "https://10.10.10.10",
-			AWSIntegrationID:         1,
-			CertificateAuthorityData: []byte("-----BEGIN CER"),
-		},
-	},
-	clusterTest{
-		name:    "aws eks get token",
-		raw:     fixtures.AWSEKSGetTokenExec,
-		isLocal: false,
-		resolver: &models.ClusterResolverAll{
-			AWSAccessKeyID:     "accesskey",
-			AWSClusterID:       "cluster-test-aws-id-guess",
-			AWSSecretAccessKey: "secret",
-		},
-		expIntegration: &ints.AWSIntegration{
-			UserID:             1,
-			ProjectID:          1,
-			AWSAccessKeyID:     []byte("accesskey"),
-			AWSClusterID:       []byte("cluster-test-aws-id-guess"),
-			AWSSecretAccessKey: []byte("secret"),
-		},
-		expCluster: &models.Cluster{
-			AuthMechanism:            models.AWS,
-			ProjectID:                1,
-			Name:                     "cluster-test",
-			Server:                   "https://10.10.10.10",
-			AWSIntegrationID:         2,
-			CertificateAuthorityData: []byte("-----BEGIN CER"),
-		},
-	},
-	clusterTest{
-		name:    "oidc without idp issuer data",
-		raw:     fixtures.OIDCAuthWithoutData,
-		isLocal: false,
-		resolver: &models.ClusterResolverAll{
-			OIDCIssuerCAData: "LS0tLS1CRUdJTiBDRVJ=",
-		},
-		expIntegration: &ints.OIDCIntegration{
-			Client:                   ints.OIDCKube,
-			UserID:                   1,
-			ProjectID:                1,
-			IssuerURL:                []byte("https://10.10.10.10"),
-			ClientID:                 []byte("porter-api"),
-			CertificateAuthorityData: []byte("LS0tLS1CRUdJTiBDRVJ="),
-			IDToken:                  []byte("token"),
-		},
-		expCluster: &models.Cluster{
-			AuthMechanism:            models.OIDC,
-			ProjectID:                1,
-			Name:                     "cluster-test",
-			Server:                   "https://10.10.10.10",
-			OIDCIntegrationID:        1,
-			CertificateAuthorityData: []byte("-----BEGIN CER"),
-		},
-	},
-	clusterTest{
-		name:     "oidc with idp issuer data",
-		raw:      fixtures.OIDCAuthWithData,
-		isLocal:  false,
-		resolver: &models.ClusterResolverAll{},
-		expIntegration: &ints.OIDCIntegration{
-			Client:                   ints.OIDCKube,
-			UserID:                   1,
-			ProjectID:                1,
-			IssuerURL:                []byte("https://10.10.10.10"),
-			ClientID:                 []byte("porter-api"),
-			CertificateAuthorityData: []byte("LS0tLS1CRUdJTiBDRVJ="),
-			IDToken:                  []byte("token"),
-		},
-		expCluster: &models.Cluster{
-			AuthMechanism:            models.OIDC,
-			ProjectID:                1,
-			Name:                     "cluster-test",
-			Server:                   "https://10.10.10.10",
-			OIDCIntegrationID:        2,
-			CertificateAuthorityData: []byte("-----BEGIN CER"),
-		},
-	},
-}
-
-func TestClusters(t *testing.T) {
-	tester := &tester{
-		dbFileName: "./cluster_test.db",
-	}
-
-	setupTestEnv(tester, t)
-	initUser(tester, t)
-	initProject(tester, t)
-	defer cleanup(tester, t)
-
-	for _, c := range ClusterTests {
-		// create cluster candidate
-		ccForm := &forms.CreateClusterCandidatesForm{
-			ProjectID:  tester.initProjects[0].ID,
-			Kubeconfig: c.raw,
-			IsLocal:    c.isLocal,
-		}
-
-		ccs, err := ccForm.ToClusterCandidates(c.isLocal)
-
-		if err != nil {
-			t.Fatalf("%v\n", err)
-		}
-
-		var cc *models.ClusterCandidate
-
-		for _, _cc := range ccs {
-			cc, err = tester.repo.Cluster().CreateClusterCandidate(_cc)
-
-			if err != nil {
-				t.Fatalf("%v\n", err)
-			}
-
-			cc, err = tester.repo.Cluster().ReadClusterCandidate(cc.ID)
-
-			if err != nil {
-				t.Fatalf("%v\n", err)
-			}
-		}
-
-		form := &forms.ResolveClusterForm{
-			Resolver:           c.resolver,
-			ClusterCandidateID: cc.ID,
-			ProjectID:          tester.initProjects[0].ID,
-			UserID:             tester.initUsers[0].ID,
-		}
-
-		// resolve integration (should be kube with local)
-		err = form.ResolveIntegration(tester.repo)
-
-		if err != nil {
-			t.Fatalf("%v\n", err)
-		}
-
-		switch c.expIntegration.(type) {
-		case *ints.KubeIntegration:
-			// make sure integration is equal, read integration from DB
-			gotIntegration, err := tester.repo.KubeIntegration().ReadKubeIntegration(form.IntegrationID)
-
-			if err != nil {
-				t.Fatalf("%v\n", err)
-			}
-
-			// reset got integration model
-			gotIntegration.Model = gorm.Model{}
-
-			ki, _ := c.expIntegration.(*ints.KubeIntegration)
-
-			// if kubeconfig, compare
-			if len(ki.Kubeconfig) > 0 {
-				compareKubeconfig(t, gotIntegration.Kubeconfig, ki.Kubeconfig)
-
-				// reset kubeconfig fields for deep.Equal
-				gotIntegration.Kubeconfig = []byte{}
-				ki.Kubeconfig = []byte{}
-			}
-
-			if diff := deep.Equal(ki, gotIntegration); diff != nil {
-				t.Errorf("incorrect kube integration")
-				t.Error(diff)
-			}
-		case *ints.OIDCIntegration:
-			// make sure integration is equal, read integration from DB
-			gotIntegration, err := tester.repo.OIDCIntegration().ReadOIDCIntegration(form.IntegrationID)
-
-			if err != nil {
-				t.Fatalf("%v\n", err)
-			}
-
-			// reset got integration model
-			gotIntegration.Model = gorm.Model{}
-
-			oidc, _ := c.expIntegration.(*ints.OIDCIntegration)
-
-			if diff := deep.Equal(oidc, gotIntegration); diff != nil {
-				t.Errorf("incorrect oidc integration")
-				t.Error(diff)
-			}
-		case *ints.GCPIntegration:
-			// make sure integration is equal, read integration from DB
-			gotIntegration, err := tester.repo.GCPIntegration().ReadGCPIntegration(form.IntegrationID)
-
-			if err != nil {
-				t.Fatalf("%v\n", err)
-			}
-
-			// reset got integration model
-			gotIntegration.Model = gorm.Model{}
-
-			gcp, _ := c.expIntegration.(*ints.GCPIntegration)
-
-			if diff := deep.Equal(gcp, gotIntegration); diff != nil {
-				t.Errorf("incorrect gcp integration")
-				t.Error(diff)
-			}
-		case *ints.AWSIntegration:
-			// make sure integration is equal, read integration from DB
-			gotIntegration, err := tester.repo.AWSIntegration().ReadAWSIntegration(form.IntegrationID)
-
-			if err != nil {
-				t.Fatalf("%v\n", err)
-			}
-
-			// reset got integration model
-			gotIntegration.Model = gorm.Model{}
-
-			aws, _ := c.expIntegration.(*ints.AWSIntegration)
-
-			if diff := deep.Equal(aws, gotIntegration); diff != nil {
-				t.Errorf("incorrect aws integration")
-				t.Error(diff)
-			}
-		}
-
-		// resolve cluster
-		gotCluster, err := form.ResolveCluster(tester.repo)
-
-		if err != nil {
-			t.Fatalf("%v\n", err)
-		}
-
-		gotCluster.Model = gorm.Model{}
-
-		if diff := deep.Equal(c.expCluster, gotCluster); diff != nil {
-			t.Errorf("incorrect cluster")
-			t.Error(diff)
-		}
-	}
-}
-
-func compareKubeconfig(t *testing.T, resKube []byte, expKube []byte) {
-	// compare kubeconfig by transforming into a client config
-	resConfig, _ := clientcmd.NewClientConfigFromBytes(resKube)
-	expConfig, err := clientcmd.NewClientConfigFromBytes(expKube)
-
-	if err != nil {
-		t.Fatalf("config from bytes, error occurred %v\n", err)
-	}
-
-	resRawConf, _ := resConfig.RawConfig()
-	expRawConf, err := expConfig.RawConfig()
-
-	if err != nil {
-		t.Fatalf("raw config conversion, error occurred %v\n", err)
-	}
-
-	if diff := deep.Equal(expRawConf, resRawConf); diff != nil {
-		t.Errorf("incorrect kubeconfigs")
-		t.Error(diff)
-	}
-}
-
-// func TestPopulateServiceAccountClusterDataAction(t *testing.T) {
-// 	// create the in-memory repository
-// 	repo := test.NewRepository(true)
-
-// 	// create a new project
-// 	repo.Project().CreateProject(&models.Project{
-// 		Name: "test-project",
-// 	})
-
-// 	// create a ServiceAccountCandidate from a kubeconfig
-// 	saCandidates, err := kubernetes.GetServiceAccountCandidates([]byte(ClusterCAWithoutData), false)
-
-// 	if err != nil {
-// 		t.Fatalf("%v\n", err)
-// 	}
-
-// 	for _, saCandidate := range saCandidates {
-// 		repo.ServiceAccount.CreateServiceAccountCandidate(saCandidate)
-// 	}
-
-// 	// create a new form
-// 	form := forms.ClusterCADataAction{
-// 		ServiceAccountActionResolver: &forms.ServiceAccountActionResolver{
-// 			ServiceAccountCandidateID: 1,
-// 		},
-// 		ClusterCAData: "LS0tLS1CRUdJTiBDRVJ=",
-// 	}
-
-// 	err = form.PopulateServiceAccount(repo.ServiceAccount)
-
-// 	if err != nil {
-// 		t.Fatalf("%v\n", err)
-// 	}
-
-// 	sa, err := repo.ServiceAccount.CreateServiceAccount(form.ServiceAccountActionResolver.SA)
-// 	decodedStr, _ := base64.StdEncoding.DecodeString("LS0tLS1CRUdJTiBDRVJ=")
-
-// 	if len(sa.Clusters) != 1 {
-// 		t.Fatalf("cluster not written\n")
-// 	}
-
-// 	if sa.Clusters[0].ServiceAccountID != 1 {
-// 		t.Errorf("service account ID of joined cluster is not 1")
-// 	}
-
-// 	if string(sa.Clusters[0].CertificateAuthorityData) != string(decodedStr) {
-// 		t.Errorf("cluster ca data and input do not match: expected %s, got %s\n",
-// 			string(sa.Clusters[0].CertificateAuthorityData), string(decodedStr))
-// 	}
-
-// 	if sa.Integration != "x509" {
-// 		t.Errorf("service account auth mechanism is not x509")
-// 	}
-
-// 	if string(sa.ClientCertificateData) != string(decodedStr) {
-// 		t.Errorf("service account cert data and input do not match: expected %s, got %s\n",
-// 			string(sa.ClientCertificateData), string(decodedStr))
-// 	}
-
-// 	if string(sa.ClientKeyData) != string(decodedStr) {
-// 		t.Errorf("service account key data and input do not match: expected %s, got %s\n",
-// 			string(sa.ClientKeyData), string(decodedStr))
-// 	}
-// }
-
-// func TestPopulateServiceAccountClusterLocalhostAction(t *testing.T) {
-// 	// create the in-memory repository
-// 	repo := test.NewRepository(true)
-
-// 	// create a new project
-// 	repo.Project().CreateProject(&models.Project{
-// 		Name: "test-project",
-// 	})
-
-// 	// create a ServiceAccountCandidate from a kubeconfig
-// 	saCandidates, err := kubernetes.GetServiceAccountCandidates([]byte(ClusterLocalhost), false)
-
-// 	if err != nil {
-// 		t.Fatalf("%v\n", err)
-// 	}
-
-// 	for _, saCandidate := range saCandidates {
-// 		repo.ServiceAccount.CreateServiceAccountCandidate(saCandidate)
-// 	}
-
-// 	// create a new form
-// 	form := forms.ClusterLocalhostAction{
-// 		ServiceAccountActionResolver: &forms.ServiceAccountActionResolver{
-// 			ServiceAccountCandidateID: 1,
-// 		},
-// 		ClusterHostname: "host.docker.internal",
-// 	}
-
-// 	err = form.PopulateServiceAccount(repo.ServiceAccount)
-
-// 	if err != nil {
-// 		t.Fatalf("%v\n", err)
-// 	}
-
-// 	sa, err := repo.ServiceAccount.CreateServiceAccount(form.ServiceAccountActionResolver.SA)
-// 	decodedStr, _ := base64.StdEncoding.DecodeString("LS0tLS1CRUdJTiBDRVJ=")
-
-// 	if len(sa.Clusters) != 1 {
-// 		t.Fatalf("cluster not written\n")
-// 	}
-
-// 	if sa.Clusters[0].ServiceAccountID != 1 {
-// 		t.Errorf("service account ID of joined cluster is not 1")
-// 	}
-
-// 	if sa.Clusters[0].Server != "https://host.docker.internal:30000" {
-// 		t.Errorf("service account cluster server is incorrect: expected %s, got %s\n",
-// 			"https://host.docker.internal:30000", sa.Clusters[0].Server)
-// 	}
-
-// 	if sa.Integration != "x509" {
-// 		t.Errorf("service account auth mechanism is not x509")
-// 	}
-
-// 	if string(sa.ClientCertificateData) != string(decodedStr) {
-// 		t.Errorf("service account cert data and input do not match: expected %s, got %s\n",
-// 			string(sa.ClientCertificateData), string(decodedStr))
-// 	}
-
-// 	if string(sa.ClientKeyData) != string(decodedStr) {
-// 		t.Errorf("service account key data and input do not match: expected %s, got %s\n",
-// 			string(sa.ClientKeyData), string(decodedStr))
-// 	}
-// }
-
-// func TestPopulateServiceAccountClientCertAction(t *testing.T) {
-// 	// create the in-memory repository
-// 	repo := test.NewRepository(true)
-
-// 	// create a new project
-// 	repo.Project().CreateProject(&models.Project{
-// 		Name: "test-project",
-// 	})
-
-// 	// create a ServiceAccountCandidate from a kubeconfig
-// 	saCandidates, err := kubernetes.GetServiceAccountCandidates([]byte(ClientWithoutCertData), false)
-
-// 	if err != nil {
-// 		t.Fatalf("%v\n", err)
-// 	}
-
-// 	for _, saCandidate := range saCandidates {
-// 		repo.ServiceAccount.CreateServiceAccountCandidate(saCandidate)
-// 	}
-
-// 	// create a new form
-// 	form := forms.ClientCertDataAction{
-// 		ServiceAccountActionResolver: &forms.ServiceAccountActionResolver{
-// 			ServiceAccountCandidateID: 1,
-// 		},
-// 		ClientCertData: "LS0tLS1CRUdJTiBDRVJ=",
-// 	}
-
-// 	err = form.PopulateServiceAccount(repo.ServiceAccount)
-
-// 	if err != nil {
-// 		t.Fatalf("%v\n", err)
-// 	}
-
-// 	sa, err := repo.ServiceAccount.CreateServiceAccount(form.ServiceAccountActionResolver.SA)
-// 	decodedStr, _ := base64.StdEncoding.DecodeString("LS0tLS1CRUdJTiBDRVJ=")
-
-// 	if len(sa.Clusters) != 1 {
-// 		t.Fatalf("cluster not written\n")
-// 	}
-
-// 	if sa.Clusters[0].ServiceAccountID != 1 {
-// 		t.Errorf("service account ID of joined cluster is not 1")
-// 	}
-
-// 	if string(sa.Clusters[0].CertificateAuthorityData) != string(decodedStr) {
-// 		t.Errorf("cluster ca data and input do not match: expected %s, got %s\n",
-// 			string(sa.Clusters[0].CertificateAuthorityData), string(decodedStr))
-// 	}
-
-// 	if sa.Integration != "x509" {
-// 		t.Errorf("service account auth mechanism is not x509")
-// 	}
-
-// 	if string(sa.ClientCertificateData) != string(decodedStr) {
-// 		t.Errorf("service account cert data and input do not match: expected %s, got %s\n",
-// 			string(sa.ClientCertificateData), string(decodedStr))
-// 	}
-
-// 	if string(sa.ClientKeyData) != string(decodedStr) {
-// 		t.Errorf("service account key data and input do not match: expected %s, got %s\n",
-// 			string(sa.ClientKeyData), string(decodedStr))
-// 	}
-// }
-
-// func TestPopulateServiceAccountClientCertAndKeyActions(t *testing.T) {
-// 	// create the in-memory repository
-// 	repo := test.NewRepository(true)
-
-// 	// create a new project
-// 	repo.Project().CreateProject(&models.Project{
-// 		Name: "test-project",
-// 	})
-
-// 	// create a ServiceAccountCandidate from a kubeconfig
-// 	saCandidates, err := kubernetes.GetServiceAccountCandidates([]byte(ClientWithoutCertAndKeyData), false)
-
-// 	if err != nil {
-// 		t.Fatalf("%v\n", err)
-// 	}
-
-// 	for _, saCandidate := range saCandidates {
-// 		repo.ServiceAccount.CreateServiceAccountCandidate(saCandidate)
-// 	}
-
-// 	// create a new form
-// 	form := forms.ClientCertDataAction{
-// 		ServiceAccountActionResolver: &forms.ServiceAccountActionResolver{
-// 			ServiceAccountCandidateID: 1,
-// 		},
-// 		ClientCertData: "LS0tLS1CRUdJTiBDRVJ=",
-// 	}
-
-// 	err = form.PopulateServiceAccount(repo.ServiceAccount)
-
-// 	if err != nil {
-// 		t.Fatalf("%v\n", err)
-// 	}
-
-// 	keyForm := forms.ClientKeyDataAction{
-// 		ServiceAccountActionResolver: form.ServiceAccountActionResolver,
-// 		ClientKeyData:                "LS0tLS1CRUdJTiBDRVJ=",
-// 	}
-
-// 	err = keyForm.PopulateServiceAccount(repo.ServiceAccount)
-
-// 	if err != nil {
-// 		t.Fatalf("%v\n", err)
-// 	}
-
-// 	sa, err := repo.ServiceAccount.CreateServiceAccount(keyForm.ServiceAccountActionResolver.SA)
-// 	decodedStr, _ := base64.StdEncoding.DecodeString("LS0tLS1CRUdJTiBDRVJ=")
-
-// 	if len(sa.Clusters) != 1 {
-// 		t.Fatalf("cluster not written\n")
-// 	}
-
-// 	if sa.Clusters[0].ServiceAccountID != 1 {
-// 		t.Errorf("service account ID of joined cluster is not 1")
-// 	}
-
-// 	if string(sa.Clusters[0].CertificateAuthorityData) != string(decodedStr) {
-// 		t.Errorf("cluster ca data and input do not match: expected %s, got %s\n",
-// 			string(sa.Clusters[0].CertificateAuthorityData), string(decodedStr))
-// 	}
-
-// 	if sa.Integration != "x509" {
-// 		t.Errorf("service account auth mechanism is not x509")
-// 	}
-
-// 	if string(sa.ClientCertificateData) != string(decodedStr) {
-// 		t.Errorf("service account cert data and input do not match: expected %s, got %s\n",
-// 			string(sa.ClientCertificateData), string(decodedStr))
-// 	}
-
-// 	if string(sa.ClientKeyData) != string(decodedStr) {
-// 		t.Errorf("service account cert data and input do not match: expected %s, got %s\n",
-// 			string(sa.ClientKeyData), string(decodedStr))
-// 	}
-// }
-
-// func TestPopulateServiceAccountTokenDataAction(t *testing.T) {
-// 	// create the in-memory repository
-// 	repo := test.NewRepository(true)
-// 	tokenData := "abcdefghijklmnop"
-
-// 	// create a new project
-// 	repo.Project().CreateProject(&models.Project{
-// 		Name: "test-project",
-// 	})
-
-// 	// create a ServiceAccountCandidate from a kubeconfig
-// 	saCandidates, err := kubernetes.GetServiceAccountCandidates([]byte(BearerTokenWithoutData), false)
-
-// 	if err != nil {
-// 		t.Fatalf("%v\n", err)
-// 	}
-
-// 	for _, saCandidate := range saCandidates {
-// 		repo.ServiceAccount.CreateServiceAccountCandidate(saCandidate)
-// 	}
-
-// 	// create a new form
-// 	form := forms.TokenDataAction{
-// 		ServiceAccountActionResolver: &forms.ServiceAccountActionResolver{
-// 			ServiceAccountCandidateID: 1,
-// 		},
-// 		TokenData: tokenData,
-// 	}
-
-// 	err = form.PopulateServiceAccount(repo.ServiceAccount)
-
-// 	if err != nil {
-// 		t.Fatalf("%v\n", err)
-// 	}
-
-// 	sa, err := repo.ServiceAccount.CreateServiceAccount(form.ServiceAccountActionResolver.SA)
-
-// 	if len(sa.Clusters) != 1 {
-// 		t.Fatalf("cluster not written\n")
-// 	}
-
-// 	if sa.Clusters[0].ServiceAccountID != 1 {
-// 		t.Errorf("service account ID of joined cluster is not 1")
-// 	}
-
-// 	if sa.Integration != models.Bearer {
-// 		t.Errorf("service account auth mechanism is not %s\n", models.Bearer)
-// 	}
-
-// 	if string(sa.Token) != tokenData {
-// 		t.Errorf("service account token data is wrong: expected %s, got %s\n",
-// 			tokenData, sa.Token)
-// 	}
-// }
-
-// func TestPopulateServiceAccountGCPKeyDataAction(t *testing.T) {
-// 	// create the in-memory repository
-// 	repo := test.NewRepository(true)
-// 	gcpKeyData := []byte(`{"key": "data"}`)
-
-// 	// create a new project
-// 	repo.Project().CreateProject(&models.Project{
-// 		Name: "test-project",
-// 	})
-
-// 	// create a ServiceAccountCandidate from a kubeconfig
-// 	saCandidates, err := kubernetes.GetServiceAccountCandidates([]byte(GCPPlugin), false)
-
-// 	if err != nil {
-// 		t.Fatalf("%v\n", err)
-// 	}
-
-// 	for _, saCandidate := range saCandidates {
-// 		repo.ServiceAccount.CreateServiceAccountCandidate(saCandidate)
-// 	}
-
-// 	// create a new form
-// 	form := forms.GCPKeyDataAction{
-// 		ServiceAccountActionResolver: &forms.ServiceAccountActionResolver{
-// 			ServiceAccountCandidateID: 1,
-// 		},
-// 		GCPKeyData: string(gcpKeyData),
-// 	}
-
-// 	err = form.PopulateServiceAccount(repo.ServiceAccount)
-
-// 	if err != nil {
-// 		t.Fatalf("%v\n", err)
-// 	}
-
-// 	sa, err := repo.ServiceAccount.CreateServiceAccount(form.ServiceAccountActionResolver.SA)
-
-// 	if len(sa.Clusters) != 1 {
-// 		t.Fatalf("cluster not written\n")
-// 	}
-
-// 	if sa.Clusters[0].ServiceAccountID != 1 {
-// 		t.Errorf("service account ID of joined cluster is not 1")
-// 	}
-
-// 	if sa.Integration != models.GCP {
-// 		t.Errorf("service account auth mechanism is not %s\n", models.GCP)
-// 	}
-
-// 	if string(sa.GCPKeyData) != string(gcpKeyData) {
-// 		t.Errorf("service account token data is wrong: expected %s, got %s\n",
-// 			string(sa.GCPKeyData), string(gcpKeyData))
-// 	}
-// }
-
-// func TestPopulateServiceAccountAWSKeyDataAction(t *testing.T) {
-// 	// create the in-memory repository
-// 	repo := test.NewRepository(true)
-
-// 	// create a new project
-// 	repo.Project().CreateProject(&models.Project{
-// 		Name: "test-project",
-// 	})
-
-// 	// create a ServiceAccountCandidate from a kubeconfig
-// 	saCandidates, err := kubernetes.GetServiceAccountCandidates([]byte(AWSEKSGetTokenExec), false)
-
-// 	if err != nil {
-// 		t.Fatalf("%v\n", err)
-// 	}
-
-// 	for _, saCandidate := range saCandidates {
-// 		repo.ServiceAccount.CreateServiceAccountCandidate(saCandidate)
-// 	}
-
-// 	// create a new form
-// 	form := forms.AWSDataAction{
-// 		ServiceAccountActionResolver: &forms.ServiceAccountActionResolver{
-// 			ServiceAccountCandidateID: 1,
-// 		},
-// 		AWSAccessKeyID:     "ALSDKJFADSF",
-// 		AWSSecretAccessKey: "ASDLFKJALSDKFJ",
-// 		AWSClusterID:       "cluster-test",
-// 	}
-
-// 	err = form.PopulateServiceAccount(repo.ServiceAccount)
-
-// 	if err != nil {
-// 		t.Fatalf("%v\n", err)
-// 	}
-
-// 	sa, err := repo.ServiceAccount.CreateServiceAccount(form.ServiceAccountActionResolver.SA)
-
-// 	if len(sa.Clusters) != 1 {
-// 		t.Fatalf("cluster not written\n")
-// 	}
-
-// 	if sa.Clusters[0].ServiceAccountID != 1 {
-// 		t.Errorf("service account ID of joined cluster is not 1")
-// 	}
-
-// 	if sa.Integration != models.AWS {
-// 		t.Errorf("service account auth mechanism is not %s\n", models.AWS)
-// 	}
-
-// 	if string(sa.AWSAccessKeyID) != "ALSDKJFADSF" {
-// 		t.Errorf("service account aws access key id is wrong: expected %s, got %s\n",
-// 			"ALSDKJFADSF", sa.AWSAccessKeyID)
-// 	}
-
-// 	if string(sa.AWSSecretAccessKey) != "ASDLFKJALSDKFJ" {
-// 		t.Errorf("service account aws access secret key is wrong: expected %s, got %s\n",
-// 			"ASDLFKJALSDKFJ", sa.AWSSecretAccessKey)
-// 	}
-
-// 	if string(sa.AWSClusterID) != "cluster-test" {
-// 		t.Errorf("service account aws cluster id is wrong: expected %s, got %s\n",
-// 			"cluster-test", sa.AWSClusterID)
-// 	}
-// }
-
-// func TestPopulateServiceAccountOIDCAction(t *testing.T) {
-// 	// create the in-memory repository
-// 	repo := test.NewRepository(true)
-
-// 	// create a new project
-// 	repo.Project().CreateProject(&models.Project{
-// 		Name: "test-project",
-// 	})
-
-// 	// create a ServiceAccountCandidate from a kubeconfig
-// 	saCandidates, err := kubernetes.GetServiceAccountCandidates([]byte(OIDCAuthWithoutData), false)
-
-// 	if err != nil {
-// 		t.Fatalf("%v\n", err)
-// 	}
-
-// 	for _, saCandidate := range saCandidates {
-// 		repo.ServiceAccount.CreateServiceAccountCandidate(saCandidate)
-// 	}
-
-// 	// create a new form
-// 	form := forms.OIDCIssuerDataAction{
-// 		ServiceAccountActionResolver: &forms.ServiceAccountActionResolver{
-// 			ServiceAccountCandidateID: 1,
-// 		},
-// 		OIDCIssuerCAData: "LS0tLS1CRUdJTiBDRVJ=",
-// 	}
-
-// 	err = form.PopulateServiceAccount(repo.ServiceAccount)
-
-// 	if err != nil {
-// 		t.Fatalf("%v\n", err)
-// 	}
-
-// 	sa, err := repo.ServiceAccount.CreateServiceAccount(form.ServiceAccountActionResolver.SA)
-
-// 	if len(sa.Clusters) != 1 {
-// 		t.Fatalf("cluster not written\n")
-// 	}
-
-// 	if sa.Clusters[0].ServiceAccountID != 1 {
-// 		t.Errorf("service account ID of joined cluster is not 1")
-// 	}
-
-// 	if sa.Integration != models.OIDC {
-// 		t.Errorf("service account auth mechanism is not %s\n", models.OIDC)
-// 	}
-
-// 	if string(sa.OIDCCertificateAuthorityData) != "LS0tLS1CRUdJTiBDRVJ=" {
-// 		t.Errorf("service account key data and input do not match: expected %s, got %s\n",
-// 			string(sa.OIDCCertificateAuthorityData), "LS0tLS1CRUdJTiBDRVJ=")
-// 	}
-// }

+ 0 - 8
internal/forms/domain.go

@@ -1,8 +0,0 @@
-package forms
-
-// CreateDomainForm represents the accepted values for creating a DNS record
-type CreateDomainForm struct {
-	*K8sForm
-
-	ReleaseName string `json:"release_name" form:"required"`
-}

+ 0 - 48
internal/forms/git_action.go

@@ -1,48 +0,0 @@
-package forms
-
-import (
-	"github.com/porter-dev/porter/internal/models"
-)
-
-// CreateGitAction represents the accepted values for creating a
-// github action integration
-type CreateGitAction struct {
-	Release *models.Release
-
-	GitRepo        string `json:"git_repo" form:"required"`
-	GitBranch      string `json:"branch"`
-	ImageRepoURI   string `json:"image_repo_uri" form:"required"`
-	DockerfilePath string `json:"dockerfile_path"`
-	FolderPath     string `json:"folder_path"`
-	GitRepoID      uint   `json:"git_repo_id" form:"required"`
-	RegistryID     uint   `json:"registry_id"`
-
-	ShouldCreateWorkflow bool `json:"should_create_workflow"`
-	ShouldGenerateOnly   bool
-}
-
-// ToGitActionConfig converts the form to a gorm git action config model
-func (ca *CreateGitAction) ToGitActionConfig(version string) (*models.GitActionConfig, error) {
-	return &models.GitActionConfig{
-		ReleaseID:            ca.Release.Model.ID,
-		GitRepo:              ca.GitRepo,
-		GitBranch:            ca.GitBranch,
-		ImageRepoURI:         ca.ImageRepoURI,
-		DockerfilePath:       ca.DockerfilePath,
-		FolderPath:           ca.FolderPath,
-		GithubInstallationID: ca.GitRepoID,
-		IsInstallation:       true,
-		Version:              version,
-	}, nil
-}
-
-type CreateGitActionOptional struct {
-	GitRepo              string `json:"git_repo"`
-	GitBranch            string `json:"branch"`
-	ImageRepoURI         string `json:"image_repo_uri"`
-	DockerfilePath       string `json:"dockerfile_path"`
-	FolderPath           string `json:"folder_path"`
-	GitRepoID            uint   `json:"git_repo_id"`
-	RegistryID           uint   `json:"registry_id"`
-	ShouldCreateWorkflow bool   `json:"should_create_workflow"`
-}

+ 0 - 29
internal/forms/helm_repo.go

@@ -1,29 +0,0 @@
-package forms
-
-import (
-	"github.com/porter-dev/porter/internal/models"
-)
-
-// CreateHelmRepo represents the accepted values for creating a
-// helm repo
-type CreateHelmRepo struct {
-	Name      string `json:"name" form:"required"`
-	RepoURL   string `json:"repo_url" form:"required"`
-	ProjectID uint   `json:"project_id" form:"required"`
-
-	BasicIntegrationID uint `json:"basic_integration_id"`
-	GCPIntegrationID   uint `json:"gcp_integration_id"`
-	AWSIntegrationID   uint `json:"aws_integration_id"`
-}
-
-// ToHelmRepo converts the form to a gorm helm repo model
-func (ch *CreateHelmRepo) ToHelmRepo() (*models.HelmRepo, error) {
-	return &models.HelmRepo{
-		Name:                   ch.Name,
-		RepoURL:                ch.RepoURL,
-		ProjectID:              ch.ProjectID,
-		BasicAuthIntegrationID: ch.BasicIntegrationID,
-		GCPIntegrationID:       ch.GCPIntegrationID,
-		AWSIntegrationID:       ch.AWSIntegrationID,
-	}, nil
-}

+ 0 - 362
internal/forms/helper_test.go

@@ -1,362 +0,0 @@
-package forms_test
-
-import (
-	"os"
-	"testing"
-
-	"github.com/porter-dev/porter/api/server/shared/config/env"
-	"github.com/porter-dev/porter/api/types"
-	"github.com/porter-dev/porter/internal/adapter"
-	"github.com/porter-dev/porter/internal/models"
-	ints "github.com/porter-dev/porter/internal/models/integrations"
-	"github.com/porter-dev/porter/internal/repository"
-	"github.com/porter-dev/porter/internal/repository/gorm"
-)
-
-type tester struct {
-	repo         repository.Repository
-	key          *[32]byte
-	dbFileName   string
-	initUsers    []*models.User
-	initProjects []*models.Project
-	initGRs      []*models.GitRepo
-	initClusters []*models.Cluster
-	initCCs      []*models.ClusterCandidate
-	initKIs      []*ints.KubeIntegration
-	initOIDCs    []*ints.OIDCIntegration
-	initOAuths   []*ints.OAuthIntegration
-	initGCPs     []*ints.GCPIntegration
-	initAWSs     []*ints.AWSIntegration
-}
-
-func setupTestEnv(tester *tester, t *testing.T) {
-	t.Helper()
-
-	db, err := adapter.New(&env.DBConf{
-		EncryptionKey: "__random_strong_encryption_key__",
-		SQLLite:       true,
-		SQLLitePath:   tester.dbFileName,
-	})
-
-	if err != nil {
-		t.Fatalf("%v\n", err)
-	}
-
-	err = db.AutoMigrate(
-		&models.Project{},
-		&models.Role{},
-		&models.Release{},
-		&models.User{},
-		&models.Session{},
-		&models.GitRepo{},
-		&models.Cluster{},
-		&models.ClusterCandidate{},
-		&models.ClusterResolver{},
-		&ints.KubeIntegration{},
-		&ints.OIDCIntegration{},
-		&ints.OAuthIntegration{},
-		&ints.GCPIntegration{},
-		&ints.AWSIntegration{},
-		&ints.TokenCache{},
-	)
-
-	if err != nil {
-		t.Fatalf("%v\n", err)
-	}
-
-	var key [32]byte
-
-	for i, b := range []byte("__random_strong_encryption_key__") {
-		key[i] = b
-	}
-
-	tester.key = &key
-
-	tester.repo = gorm.NewRepository(db, &key)
-}
-
-func cleanup(tester *tester, t *testing.T) {
-	t.Helper()
-
-	// remove the created file file
-	os.Remove(tester.dbFileName)
-}
-
-func initUser(tester *tester, t *testing.T) {
-	t.Helper()
-
-	user := &models.User{
-		Email:    "example@example.com",
-		Password: "hello1234",
-	}
-
-	user, err := tester.repo.User().CreateUser(user)
-
-	if err != nil {
-		t.Fatalf("%v\n", err)
-	}
-
-	tester.initUsers = append(tester.initUsers, user)
-}
-
-func initProject(tester *tester, t *testing.T) {
-	t.Helper()
-
-	proj := &models.Project{
-		Name: "project-test",
-	}
-
-	proj, err := tester.repo.Project().CreateProject(proj)
-
-	if err != nil {
-		t.Fatalf("%v\n", err)
-	}
-
-	tester.initProjects = append(tester.initProjects, proj)
-}
-
-func initProjectRole(tester *tester, t *testing.T) {
-	t.Helper()
-
-	role := &models.Role{
-		Role: types.Role{
-			Kind:      types.RoleAdmin,
-			UserID:    tester.initUsers[0].Model.ID,
-			ProjectID: tester.initProjects[0].Model.ID,
-		},
-	}
-
-	role, err := tester.repo.Project().CreateProjectRole(tester.initProjects[0], role)
-
-	if err != nil {
-		t.Fatalf("%v\n", err)
-	}
-}
-
-func initKubeIntegration(tester *tester, t *testing.T) {
-	t.Helper()
-
-	if len(tester.initProjects) == 0 {
-		initProject(tester, t)
-	}
-
-	if len(tester.initUsers) == 0 {
-		initUser(tester, t)
-	}
-
-	ki := &ints.KubeIntegration{
-		Mechanism:  ints.KubeLocal,
-		ProjectID:  tester.initProjects[0].ID,
-		UserID:     tester.initUsers[0].ID,
-		Kubeconfig: []byte("current-context: testing\n"),
-	}
-
-	ki, err := tester.repo.KubeIntegration().CreateKubeIntegration(ki)
-
-	if err != nil {
-		t.Fatalf("%v\n", err)
-	}
-
-	tester.initKIs = append(tester.initKIs, ki)
-}
-
-func initOIDCIntegration(tester *tester, t *testing.T) {
-	t.Helper()
-
-	if len(tester.initProjects) == 0 {
-		initProject(tester, t)
-	}
-
-	if len(tester.initUsers) == 0 {
-		initUser(tester, t)
-	}
-
-	oidc := &ints.OIDCIntegration{
-		Client:       ints.OIDCKube,
-		ProjectID:    tester.initProjects[0].ID,
-		UserID:       tester.initUsers[0].ID,
-		IssuerURL:    []byte("https://oidc.example.com"),
-		ClientID:     []byte("exampleclientid"),
-		ClientSecret: []byte("exampleclientsecret"),
-		IDToken:      []byte("idtoken"),
-		RefreshToken: []byte("refreshtoken"),
-	}
-
-	oidc, err := tester.repo.OIDCIntegration().CreateOIDCIntegration(oidc)
-
-	if err != nil {
-		t.Fatalf("%v\n", err)
-	}
-
-	tester.initOIDCs = append(tester.initOIDCs, oidc)
-}
-
-func initOAuthIntegration(tester *tester, t *testing.T) {
-	t.Helper()
-
-	if len(tester.initProjects) == 0 {
-		initProject(tester, t)
-	}
-
-	if len(tester.initUsers) == 0 {
-		initUser(tester, t)
-	}
-
-	oauth := &ints.OAuthIntegration{
-		SharedOAuthModel: ints.SharedOAuthModel{
-			ClientID:     []byte("exampleclientid"),
-			AccessToken:  []byte("idtoken"),
-			RefreshToken: []byte("refreshtoken"),
-		},
-		Client:    types.OAuthGithub,
-		ProjectID: tester.initProjects[0].ID,
-		UserID:    tester.initUsers[0].ID,
-	}
-
-	oauth, err := tester.repo.OAuthIntegration().CreateOAuthIntegration(oauth)
-
-	if err != nil {
-		t.Fatalf("%v\n", err)
-	}
-
-	tester.initOAuths = append(tester.initOAuths, oauth)
-}
-
-func initGCPIntegration(tester *tester, t *testing.T) {
-	t.Helper()
-
-	if len(tester.initProjects) == 0 {
-		initProject(tester, t)
-	}
-
-	if len(tester.initUsers) == 0 {
-		initUser(tester, t)
-	}
-
-	gcp := &ints.GCPIntegration{
-		ProjectID:    tester.initProjects[0].ID,
-		UserID:       tester.initUsers[0].ID,
-		GCPProjectID: "test-proj-123456",
-		GCPUserEmail: "test@test.it",
-		GCPKeyData:   []byte("{\"test\":\"key\"}"),
-	}
-
-	gcp, err := tester.repo.GCPIntegration().CreateGCPIntegration(gcp)
-
-	if err != nil {
-		t.Fatalf("%v\n", err)
-	}
-
-	tester.initGCPs = append(tester.initGCPs, gcp)
-}
-
-func initAWSIntegration(tester *tester, t *testing.T) {
-	t.Helper()
-
-	if len(tester.initProjects) == 0 {
-		initProject(tester, t)
-	}
-
-	if len(tester.initUsers) == 0 {
-		initUser(tester, t)
-	}
-
-	aws := &ints.AWSIntegration{
-		ProjectID:          tester.initProjects[0].ID,
-		UserID:             tester.initUsers[0].ID,
-		AWSClusterID:       []byte("example-cluster-0"),
-		AWSAccessKeyID:     []byte("accesskey"),
-		AWSSecretAccessKey: []byte("secret"),
-		AWSSessionToken:    []byte("optional"),
-	}
-
-	aws, err := tester.repo.AWSIntegration().CreateAWSIntegration(aws)
-
-	if err != nil {
-		t.Fatalf("%v\n", err)
-	}
-
-	tester.initAWSs = append(tester.initAWSs, aws)
-}
-
-func initClusterCandidate(tester *tester, t *testing.T) {
-	t.Helper()
-
-	if len(tester.initProjects) == 0 {
-		initProject(tester, t)
-	}
-
-	cc := &models.ClusterCandidate{
-		AuthMechanism:     models.AWS,
-		ProjectID:         tester.initProjects[0].ID,
-		CreatedClusterID:  0,
-		Resolvers:         []models.ClusterResolver{},
-		Name:              "cluster-test",
-		Server:            "https://localhost",
-		ContextName:       "context-test",
-		AWSClusterIDGuess: []byte("example-cluster-0"),
-		Kubeconfig:        []byte("current-context: testing\n"),
-	}
-
-	cc, err := tester.repo.Cluster().CreateClusterCandidate(cc)
-
-	if err != nil {
-		t.Fatalf("%v\n", err)
-	}
-
-	tester.initCCs = append(tester.initCCs, cc)
-}
-
-func initCluster(tester *tester, t *testing.T) {
-	t.Helper()
-
-	if len(tester.initProjects) == 0 {
-		initProject(tester, t)
-	}
-
-	if len(tester.initKIs) == 0 {
-		initKubeIntegration(tester, t)
-	}
-
-	cluster := &models.Cluster{
-		ProjectID:                tester.initProjects[0].ID,
-		Name:                     "cluster-test",
-		Server:                   "https://localhost",
-		KubeIntegrationID:        tester.initKIs[0].ID,
-		CertificateAuthorityData: []byte("-----BEGIN"),
-	}
-
-	cluster, err := tester.repo.Cluster().CreateCluster(cluster)
-
-	if err != nil {
-		t.Fatalf("%v\n", err)
-	}
-
-	tester.initClusters = append(tester.initClusters, cluster)
-}
-
-func initGitRepo(tester *tester, t *testing.T) {
-	t.Helper()
-
-	if len(tester.initProjects) == 0 {
-		initProject(tester, t)
-	}
-
-	if len(tester.initOAuths) == 0 {
-		initOAuthIntegration(tester, t)
-	}
-
-	gr := &models.GitRepo{
-		ProjectID:          tester.initProjects[0].ID,
-		RepoEntity:         "porter-dev",
-		OAuthIntegrationID: tester.initOAuths[0].ID,
-	}
-
-	gr, err := tester.repo.GitRepo().CreateGitRepo(gr)
-
-	if err != nil {
-		t.Fatalf("%v\n", err)
-	}
-
-	tester.initGRs = append(tester.initGRs, gr)
-}

+ 0 - 186
internal/forms/infra.go

@@ -1,186 +0,0 @@
-package forms
-
-import (
-	"math/rand"
-	"time"
-
-	"github.com/porter-dev/porter/api/types"
-	"github.com/porter-dev/porter/internal/models"
-)
-
-const randCharset string = "abcdefghijklmnopqrstuvwxyz1234567890"
-
-// CreateTestInfra represents the accepted values for creating test
-// infra via the provisioning container
-type CreateTestInfra struct {
-	ProjectID uint `json:"project_id" form:"required"`
-}
-
-// ToInfra converts the form to a gorm aws infra model
-func (ce *CreateTestInfra) ToInfra() (*models.Infra, error) {
-	return &models.Infra{
-		Kind:      types.InfraTest,
-		ProjectID: ce.ProjectID,
-		Suffix:    stringWithCharset(6, randCharset),
-		Status:    types.StatusCreating,
-	}, nil
-}
-
-// CreateECRInfra represents the accepted values for creating an
-// ECR infra via the provisioning container
-type CreateECRInfra struct {
-	ECRName          string `json:"ecr_name" form:"required"`
-	ProjectID        uint   `json:"project_id" form:"required"`
-	AWSIntegrationID uint   `json:"aws_integration_id" form:"required"`
-}
-
-// ToInfra converts the form to a gorm aws infra model
-func (ce *CreateECRInfra) ToInfra() (*models.Infra, error) {
-	return &models.Infra{
-		Kind:             types.InfraECR,
-		ProjectID:        ce.ProjectID,
-		Suffix:           stringWithCharset(6, randCharset),
-		Status:           types.StatusCreating,
-		AWSIntegrationID: ce.AWSIntegrationID,
-	}, nil
-}
-
-// CreateEKSInfra represents the accepted values for creating an
-// EKS infra via the provisioning container
-type CreateEKSInfra struct {
-	EKSName          string `json:"eks_name" form:"required"`
-	MachineType      string `json:"machine_type"`
-	ProjectID        uint   `json:"project_id" form:"required"`
-	AWSIntegrationID uint   `json:"aws_integration_id" form:"required"`
-}
-
-// ToInfra converts the form to a gorm aws infra model
-func (ce *CreateEKSInfra) ToInfra() (*models.Infra, error) {
-	return &models.Infra{
-		Kind:             types.InfraEKS,
-		ProjectID:        ce.ProjectID,
-		Suffix:           stringWithCharset(6, randCharset),
-		Status:           types.StatusCreating,
-		AWSIntegrationID: ce.AWSIntegrationID,
-	}, nil
-}
-
-// CreateGCRInfra represents the accepted values for creating an
-// GCR infra via the provisioning container
-type CreateGCRInfra struct {
-	ProjectID        uint `json:"project_id" form:"required"`
-	GCPIntegrationID uint `json:"gcp_integration_id" form:"required"`
-}
-
-// ToInfra converts the form to a gorm aws infra model
-func (ce *CreateGCRInfra) ToInfra() (*models.Infra, error) {
-	return &models.Infra{
-		Kind:             types.InfraGCR,
-		ProjectID:        ce.ProjectID,
-		Suffix:           stringWithCharset(6, randCharset),
-		Status:           types.StatusCreating,
-		GCPIntegrationID: ce.GCPIntegrationID,
-	}, nil
-}
-
-// CreateGKEInfra represents the accepted values for creating a
-// GKE infra via the provisioning container
-type CreateGKEInfra struct {
-	GKEName          string `json:"gke_name" form:"required"`
-	ProjectID        uint   `json:"project_id" form:"required"`
-	GCPIntegrationID uint   `json:"gcp_integration_id" form:"required"`
-}
-
-// ToInfra converts the form to a gorm aws infra model
-func (ce *CreateGKEInfra) ToInfra() (*models.Infra, error) {
-	return &models.Infra{
-		Kind:             types.InfraGKE,
-		ProjectID:        ce.ProjectID,
-		Suffix:           stringWithCharset(6, randCharset),
-		Status:           types.StatusCreating,
-		GCPIntegrationID: ce.GCPIntegrationID,
-	}, nil
-}
-
-// CreateDOCRInfra represents the accepted values for creating an
-// DOCR infra via the provisioning container
-type CreateDOCRInfra struct {
-	DOCRName             string `json:"docr_name" form:"required"`
-	DOCRSubscriptionTier string `json:"docr_subscription_tier" form:"required"`
-	ProjectID            uint   `json:"project_id" form:"required"`
-	DOIntegrationID      uint   `json:"do_integration_id" form:"required"`
-}
-
-// ToInfra converts the form to a gorm infra model
-func (de *CreateDOCRInfra) ToInfra() (*models.Infra, error) {
-	return &models.Infra{
-		Kind:            types.InfraDOCR,
-		ProjectID:       de.ProjectID,
-		Suffix:          stringWithCharset(6, randCharset),
-		Status:          types.StatusCreating,
-		DOIntegrationID: de.DOIntegrationID,
-	}, nil
-}
-
-// CreateDOKSInfra represents the accepted values for creating a
-// DOKS infra via the provisioning container
-type CreateDOKSInfra struct {
-	DORegion        string `json:"do_region" form:"required"`
-	DOKSName        string `json:"doks_name" form:"required"`
-	ProjectID       uint   `json:"project_id" form:"required"`
-	DOIntegrationID uint   `json:"do_integration_id" form:"required"`
-}
-
-// ToInfra converts the form to a gorm infra model
-func (de *CreateDOKSInfra) ToInfra() (*models.Infra, error) {
-	return &models.Infra{
-		Kind:            types.InfraDOKS,
-		ProjectID:       de.ProjectID,
-		Suffix:          stringWithCharset(6, randCharset),
-		Status:          types.StatusCreating,
-		DOIntegrationID: de.DOIntegrationID,
-	}, nil
-}
-
-// DestroyECRInfra represents the accepted values for destroying an
-// ECR infra via the provisioning container
-type DestroyECRInfra struct {
-	ECRName string `json:"ecr_name" form:"required"`
-}
-
-// DestroyEKSInfra represents the accepted values for destroying an
-// EKS infra via the provisioning container
-type DestroyEKSInfra struct {
-	EKSName string `json:"eks_name" form:"required"`
-}
-
-// DestroyGKEInfra represents the accepted values for destroying an
-// GKE infra via the provisioning container
-type DestroyGKEInfra struct {
-	GKEName string `json:"gke_name" form:"required"`
-}
-
-// DestroyDOCRInfra represents the accepted values for destroying an
-// DOCR infra via the provisioning container
-type DestroyDOCRInfra struct {
-	DOCRName string `json:"docr_name" form:"required"`
-}
-
-// DestroyDOKSInfra represents the accepted values for destroying an
-// DOKS infra via the provisioning container
-type DestroyDOKSInfra struct {
-	DOKSName string `json:"doks_name" form:"required"`
-}
-
-// helpers for random string
-var seededRand *rand.Rand = rand.New(
-	rand.NewSource(time.Now().UnixNano()))
-
-// stringWithCharset returns a random string by pulling from a given charset
-func stringWithCharset(length int, charset string) string {
-	b := make([]byte, length)
-	for i := range b {
-		b[i] = charset[seededRand.Intn(len(charset))]
-	}
-	return string(b)
-}

+ 0 - 77
internal/forms/integration.go

@@ -1,77 +0,0 @@
-package forms
-
-import (
-	ints "github.com/porter-dev/porter/internal/models/integrations"
-)
-
-// CreateGCPIntegrationForm represents the accepted values for creating a
-// GCP Integration
-type CreateGCPIntegrationForm struct {
-	UserID       uint   `json:"user_id" form:"required"`
-	ProjectID    uint   `json:"project_id" form:"required"`
-	GCPKeyData   string `json:"gcp_key_data" form:"required"`
-	GCPProjectID string `json:"gcp_project_id"`
-	GCPRegion    string `json:"gcp_region"`
-}
-
-// ToGCPIntegration converts the project to a gorm project model
-func (cgf *CreateGCPIntegrationForm) ToGCPIntegration() (*ints.GCPIntegration, error) {
-	return &ints.GCPIntegration{
-		UserID:       cgf.UserID,
-		ProjectID:    cgf.ProjectID,
-		GCPKeyData:   []byte(cgf.GCPKeyData),
-		GCPProjectID: cgf.GCPProjectID,
-		GCPRegion:    cgf.GCPRegion,
-	}, nil
-}
-
-// CreateBasicAuthIntegrationForm represents the accepted values for creating a
-// basic auth integration
-type CreateBasicAuthIntegrationForm struct {
-	UserID    uint   `json:"user_id" form:"required"`
-	ProjectID uint   `json:"project_id" form:"required"`
-	Username  string `json:"username"`
-	Password  string `json:"password"`
-}
-
-// ToBasicIntegration converts the project to a gorm project model
-func (cbf *CreateBasicAuthIntegrationForm) ToBasicIntegration() (*ints.BasicIntegration, error) {
-	return &ints.BasicIntegration{
-		UserID:    cbf.UserID,
-		ProjectID: cbf.ProjectID,
-		Username:  []byte(cbf.Username),
-		Password:  []byte(cbf.Password),
-	}, nil
-}
-
-// CreateAWSIntegrationForm represents the accepted values for creating an
-// AWS Integration
-type CreateAWSIntegrationForm struct {
-	UserID             uint   `json:"user_id" form:"required"`
-	ProjectID          uint   `json:"project_id" form:"required"`
-	AWSRegion          string `json:"aws_region"`
-	AWSClusterID       string `json:"aws_cluster_id"`
-	AWSAccessKeyID     string `json:"aws_access_key_id"`
-	AWSSecretAccessKey string `json:"aws_secret_access_key"`
-}
-
-// ToAWSIntegration converts the project to a gorm project model
-func (caf *CreateAWSIntegrationForm) ToAWSIntegration() (*ints.AWSIntegration, error) {
-	return &ints.AWSIntegration{
-		UserID:             caf.UserID,
-		ProjectID:          caf.ProjectID,
-		AWSRegion:          caf.AWSRegion,
-		AWSClusterID:       []byte(caf.AWSClusterID),
-		AWSAccessKeyID:     []byte(caf.AWSAccessKeyID),
-		AWSSecretAccessKey: []byte(caf.AWSSecretAccessKey),
-	}, nil
-}
-
-// OverwriteAWSIntegrationForm represents the accepted values for overwriting an
-// AWS Integration
-type OverwriteAWSIntegrationForm struct {
-	UserID             uint   `json:"user_id" form:"required"`
-	ProjectID          uint   `json:"project_id" form:"required"`
-	AWSAccessKeyID     string `json:"aws_access_key_id"`
-	AWSSecretAccessKey string `json:"aws_secret_access_key"`
-}

+ 0 - 30
internal/forms/invite.go

@@ -1,30 +0,0 @@
-package forms
-
-import (
-	"time"
-
-	"github.com/porter-dev/porter/internal/models"
-	"github.com/porter-dev/porter/internal/oauth"
-)
-
-// CreateInvite represents the accepted values for creating an
-// invite to a project
-type CreateInvite struct {
-	Email     string `json:"email" form:"required"`
-	Kind      string `json:"kind" form:"required"`
-	ProjectID uint   `form:"required"`
-}
-
-// ToInvite converts the project to a gorm project model
-func (ci *CreateInvite) ToInvite() (*models.Invite, error) {
-	// generate a token and an expiry time
-	expiry := time.Now().Add(24 * time.Hour)
-
-	return &models.Invite{
-		Email:     ci.Email,
-		Kind:      ci.Kind,
-		Expiry:    &expiry,
-		ProjectID: ci.ProjectID,
-		Token:     oauth.CreateRandomState(),
-	}, nil
-}

+ 0 - 56
internal/forms/k8s.go

@@ -1,56 +0,0 @@
-package forms
-
-import (
-	"net/url"
-	"strconv"
-
-	"github.com/porter-dev/porter/internal/kubernetes"
-	"github.com/porter-dev/porter/internal/repository"
-)
-
-// K8sForm is the generic base type for CRUD operations on k8s objects
-type K8sForm struct {
-	*kubernetes.OutOfClusterConfig
-}
-
-// PopulateK8sOptionsFromQueryParams populates fields in the ReleaseForm using the passed
-// url.Values (the parsed query params)
-func (kf *K8sForm) PopulateK8sOptionsFromQueryParams(
-	vals url.Values,
-	repo repository.ClusterRepository,
-) error {
-	if clusterID, ok := vals["cluster_id"]; ok && len(clusterID) == 1 {
-		id, err := strconv.ParseUint(clusterID[0], 10, 64)
-
-		if err != nil {
-			return err
-		}
-
-		cluster, err := repo.ReadCluster(uint(id))
-
-		if err != nil {
-			return err
-		}
-
-		kf.Cluster = cluster
-	}
-
-	return nil
-}
-
-type ConfigMapForm struct {
-	Name               string            `json:"name" form:"required"`
-	Namespace          string            `json:"namespace" form:"required"`
-	EnvVariables       map[string]string `json:"variables"`
-	SecretEnvVariables map[string]string `json:"secret_variables"`
-}
-
-type RenameConfigMapForm struct {
-	Name      string `json:"name" form:"required"`
-	Namespace string `json:"namespace" form:"required"`
-	NewName   string `json:"new_name" form:"required"`
-}
-
-type NamespaceForm struct {
-	Name string `json:"name" form:"required"`
-}

+ 0 - 12
internal/forms/metrics.go

@@ -1,12 +0,0 @@
-package forms
-
-import (
-	"github.com/porter-dev/porter/internal/kubernetes/prometheus"
-)
-
-// MetricsQueryForm is the form for querying pod usage metrics (cpu, memory)
-type MetricsQueryForm struct {
-	*K8sForm
-
-	*prometheus.QueryOpts
-}

+ 0 - 30
internal/forms/project.go

@@ -1,30 +0,0 @@
-package forms
-
-import (
-	"github.com/porter-dev/porter/internal/models"
-	"github.com/porter-dev/porter/internal/repository"
-)
-
-// WriteProjectForm is a generic form for write operations to the Project model
-type WriteProjectForm interface {
-	ToProject(repo repository.ProjectRepository) (*models.Project, error)
-}
-
-// CreateProjectForm represents the accepted values for creating a project
-type CreateProjectForm struct {
-	WriteProjectForm
-	Name string `json:"name" form:"required"`
-}
-
-// ToProject converts the project to a gorm project model
-func (cpf *CreateProjectForm) ToProject(_ repository.ProjectRepository) (*models.Project, error) {
-	return &models.Project{
-		Name: cpf.Name,
-	}, nil
-}
-
-// UpdateProjectRoleForm represents the accepted values for updating a project
-// role
-type UpdateProjectRoleForm struct {
-	Kind string `json:"kind"`
-}

+ 0 - 85
internal/forms/registry.go

@@ -1,85 +0,0 @@
-package forms
-
-import (
-	"github.com/aws/aws-sdk-go/service/ecr"
-	"github.com/porter-dev/porter/internal/models"
-	"github.com/porter-dev/porter/internal/repository"
-)
-
-// CreateRegistry represents the accepted values for creating a
-// registry
-type CreateRegistry struct {
-	Name               string `json:"name" form:"required"`
-	ProjectID          uint   `json:"project_id" form:"required"`
-	URL                string `json:"url"`
-	GCPIntegrationID   uint   `json:"gcp_integration_id"`
-	AWSIntegrationID   uint   `json:"aws_integration_id"`
-	DOIntegrationID    uint   `json:"do_integration_id"`
-	BasicIntegrationID uint   `json:"basic_integration_id"`
-}
-
-// ToRegistry converts the form to a gorm registry model
-func (cr *CreateRegistry) ToRegistry(repo repository.Repository) (*models.Registry, error) {
-	registry := &models.Registry{
-		Name:               cr.Name,
-		ProjectID:          cr.ProjectID,
-		URL:                cr.URL,
-		GCPIntegrationID:   cr.GCPIntegrationID,
-		AWSIntegrationID:   cr.AWSIntegrationID,
-		DOIntegrationID:    cr.DOIntegrationID,
-		BasicIntegrationID: cr.BasicIntegrationID,
-	}
-
-	if registry.URL == "" && registry.AWSIntegrationID != 0 {
-		awsInt, err := repo.AWSIntegration().ReadAWSIntegration(registry.AWSIntegrationID)
-
-		if err != nil {
-			return nil, err
-		}
-
-		sess, err := awsInt.GetSession()
-
-		if err != nil {
-			return nil, err
-		}
-
-		ecrSvc := ecr.New(sess)
-
-		output, err := ecrSvc.GetAuthorizationToken(&ecr.GetAuthorizationTokenInput{})
-
-		if err != nil {
-			return nil, err
-		}
-
-		registry.URL = *output.AuthorizationData[0].ProxyEndpoint
-	}
-
-	return registry, nil
-}
-
-// UpdateRegistryForm represents the accepted values for updating a
-// registry (only name for now)
-type UpdateRegistryForm struct {
-	ID uint
-
-	Name string `json:"name" form:"required"`
-}
-
-// ToRegistry converts the form to a cluster
-func (urf *UpdateRegistryForm) ToRegistry(repo repository.RegistryRepository) (*models.Registry, error) {
-	registry, err := repo.ReadRegistry(urf.ID)
-
-	if err != nil {
-		return nil, err
-	}
-
-	registry.Name = urf.Name
-
-	return registry, nil
-}
-
-// CreateRepository represents the accepted values for creating an image repository
-// within a registry
-type CreateRepository struct {
-	ImageRepoURI string `json:"image_repo_uri" form:"required"`
-}

+ 0 - 139
internal/forms/release.go

@@ -1,139 +0,0 @@
-package forms
-
-import (
-	"net/url"
-	"strconv"
-
-	"github.com/porter-dev/porter/internal/helm"
-	"github.com/porter-dev/porter/internal/repository"
-)
-
-// ReleaseForm is the generic base type for CRUD operations on releases
-type ReleaseForm struct {
-	*helm.Form
-}
-
-// PopulateHelmOptionsFromQueryParams populates fields in the ReleaseForm using the passed
-// url.Values (the parsed query params)
-func (rf *ReleaseForm) PopulateHelmOptionsFromQueryParams(
-	vals url.Values,
-	repo repository.ClusterRepository,
-) error {
-	if clusterID, ok := vals["cluster_id"]; ok && len(clusterID) == 1 {
-		id, err := strconv.ParseUint(clusterID[0], 10, 64)
-
-		if err != nil {
-			return err
-		}
-
-		cluster, err := repo.ReadCluster(uint(id))
-
-		if err != nil {
-			return err
-		}
-		rf.Cluster = cluster
-	}
-
-	if namespace, ok := vals["namespace"]; ok && len(namespace) == 1 {
-		rf.Namespace = namespace[0]
-	}
-
-	if storage, ok := vals["storage"]; ok && len(storage) == 1 {
-		rf.Storage = storage[0]
-	}
-
-	return nil
-}
-
-// ListReleaseForm represents the accepted values for listing Helm releases
-type ListReleaseForm struct {
-	*ReleaseForm
-	*helm.ListFilter
-}
-
-// PopulateListFromQueryParams populates fields in the ListReleaseForm using the passed
-// url.Values (the parsed query params)
-func (lrf *ListReleaseForm) PopulateListFromQueryParams(
-	vals url.Values,
-	_ repository.ClusterRepository,
-) error {
-	if namespace, ok := vals["namespace"]; ok && len(namespace) == 1 {
-		lrf.ListFilter.Namespace = namespace[0]
-	}
-
-	if limit, ok := vals["limit"]; ok && len(limit) == 1 {
-		if limitInt, err := strconv.ParseInt(limit[0], 10, 64); err == nil {
-			lrf.ListFilter.Limit = int(limitInt)
-		}
-	}
-
-	if skip, ok := vals["skip"]; ok && len(skip) == 1 {
-		if skipInt, err := strconv.ParseInt(skip[0], 10, 64); err == nil {
-			lrf.ListFilter.Skip = int(skipInt)
-		}
-	}
-
-	if byDate, ok := vals["byDate"]; ok && len(byDate) == 1 {
-		if byDateBool, err := strconv.ParseBool(byDate[0]); err == nil {
-			lrf.ListFilter.ByDate = byDateBool
-		}
-	}
-
-	if statusFilter, ok := vals["statusFilter"]; ok {
-		lrf.ListFilter.StatusFilter = statusFilter
-	}
-
-	return nil
-}
-
-// GetReleaseForm represents the accepted values for getting a single Helm release
-type GetReleaseForm struct {
-	*ReleaseForm
-	Name     string `json:"name" form:"required"`
-	Revision int    `json:"revision"`
-}
-
-// ListReleaseHistoryForm represents the accepted values for getting a single Helm release
-type ListReleaseHistoryForm struct {
-	*ReleaseForm
-	Name string `json:"name" form:"required"`
-}
-
-// RollbackReleaseForm represents the accepted values for getting a single Helm release
-type RollbackReleaseForm struct {
-	*ReleaseForm
-	Name     string `json:"name" form:"required"`
-	Revision int    `json:"revision" form:"required"`
-}
-
-// UpgradeReleaseForm represents the accepted values for updating a Helm release
-type UpgradeReleaseForm struct {
-	*ReleaseForm
-	Name         string `json:"name" form:"required"`
-	Values       string `json:"values" form:"required"`
-	ChartVersion string `json:"version"`
-}
-
-// ChartTemplateForm represents the accepted values for installing a new chart from a template.
-type ChartTemplateForm struct {
-	TemplateName string                 `json:"templateName" form:"required"`
-	ImageURL     string                 `json:"imageURL" form:"required"`
-	FormValues   map[string]interface{} `json:"formValues"`
-	Name         string                 `json:"name"`
-}
-
-// InstallChartTemplateForm represents the accepted values for installing a new chart from a template.
-type InstallChartTemplateForm struct {
-	*ReleaseForm
-	*ChartTemplateForm
-
-	// optional git action config
-	GithubActionConfig *CreateGitActionOptional `json:"githubActionConfig,omitempty"`
-}
-
-// UpdateImageForm represents the accepted values for updating a Helm release's image
-type UpdateImageForm struct {
-	*ReleaseForm
-	ImageRepoURI string `json:"image_repo_uri" form:"required"`
-	Tag          string `json:"tag" form:"required"`
-}

+ 0 - 120
internal/forms/user.go

@@ -1,120 +0,0 @@
-package forms
-
-import (
-	"time"
-
-	"github.com/porter-dev/porter/internal/models"
-	"github.com/porter-dev/porter/internal/repository"
-	"golang.org/x/crypto/bcrypt"
-	"gorm.io/gorm"
-)
-
-// WriteUserForm is a generic form for write operations to the User model
-type WriteUserForm interface {
-	ToUser(repo repository.UserRepository) (*models.User, error)
-}
-
-// CreateUserForm represents the accepted values for creating a user
-type CreateUserForm struct {
-	WriteUserForm
-	Email    string `json:"email" form:"required,max=255,email"`
-	Password string `json:"password" form:"required,max=255"`
-
-	// ignore this field from the json
-	EmailVerified bool `json:"-"`
-}
-
-// ToUser converts a CreateUserForm to models.User
-func (cuf *CreateUserForm) ToUser(_ repository.UserRepository) (*models.User, error) {
-	hashed, err := bcrypt.GenerateFromPassword([]byte(cuf.Password), 8)
-
-	if err != nil {
-		return nil, err
-	}
-
-	return &models.User{
-		Email:         cuf.Email,
-		Password:      string(hashed),
-		EmailVerified: cuf.EmailVerified,
-	}, nil
-}
-
-// LoginUserForm represents the accepted values for logging a user in
-type LoginUserForm struct {
-	WriteUserForm
-	ID       uint   `form:"required"`
-	Email    string `json:"email" form:"required,max=255,email"`
-	Password string `json:"password" form:"required,max=255"`
-}
-
-// ToUser converts a LoginUserForm to models.User
-func (luf *LoginUserForm) ToUser(_ repository.UserRepository) (*models.User, error) {
-	hashed, err := bcrypt.GenerateFromPassword([]byte(luf.Password), 8)
-
-	if err != nil {
-		return nil, err
-	}
-
-	return &models.User{
-		Email:    luf.Email,
-		Password: string(hashed),
-	}, nil
-}
-
-// DeleteUserForm represents the accepted values for deleting a user
-type DeleteUserForm struct {
-	WriteUserForm
-	ID       uint   `form:"required"`
-	Password string `json:"password" form:"required,max=255"`
-}
-
-// ToUser converts a DeleteUserForm to models.User using the user ID
-func (uuf *DeleteUserForm) ToUser(_ repository.UserRepository) (*models.User, error) {
-	return &models.User{
-		Model: gorm.Model{
-			ID: uuf.ID,
-		},
-	}, nil
-}
-
-// InitiateResetUserPasswordForm represents the accepted values for resetting a user's password
-type InitiateResetUserPasswordForm struct {
-	Email string `json:"email" form:"required"`
-}
-
-func (ruf *InitiateResetUserPasswordForm) ToPWResetToken() (*models.PWResetToken, string, error) {
-	expiry := time.Now().Add(30 * time.Minute)
-
-	rawToken := stringWithCharset(32, randCharset)
-
-	hashedToken, err := bcrypt.GenerateFromPassword([]byte(rawToken), 8)
-
-	if err != nil {
-		return nil, "", err
-	}
-
-	return &models.PWResetToken{
-		Email:   ruf.Email,
-		IsValid: true,
-		Expiry:  &expiry,
-		Token:   string(hashedToken),
-	}, rawToken, nil
-}
-
-type VerifyResetUserPasswordForm struct {
-	Email          string `json:"email" form:"required,max=255,email"`
-	PWResetTokenID uint   `json:"token_id" form:"required"`
-	Token          string `json:"token" form:"required"`
-}
-
-type FinalizeResetUserPasswordForm struct {
-	Email          string `json:"email" form:"required,max=255,email"`
-	PWResetTokenID uint   `json:"token_id" form:"required"`
-	Token          string `json:"token" form:"required"`
-	NewPassword    string `json:"new_password" form:"required,max=255"`
-}
-
-type FinalizeVerifyEmailForm struct {
-	TokenID uint   `json:"token_id" form:"required"`
-	Token   string `json:"token" form:"required"`
-}

+ 4 - 0
internal/repository/test/auth.go

@@ -464,6 +464,10 @@ func (repo *GithubAppInstallationRepository) ReadGithubAppInstallation(projectID
 	return repo.githubAppInstallations[int(id-1)], nil
 }
 
+func (repo *GithubAppInstallationRepository) ReadGithubAppInstallationByInstallationID(gaID uint) (*ints.GithubAppInstallation, error) {
+	panic("unimplemented")
+}
+
 func (repo *GithubAppInstallationRepository) ReadGithubAppInstallationByAccountID(accountID int64) (*ints.GithubAppInstallation, error) {
 
 	if !repo.canQuery {

+ 0 - 382
server/api/api.go

@@ -1,382 +0,0 @@
-package api
-
-import (
-	"fmt"
-	"net/http"
-	"strconv"
-	"strings"
-
-	"github.com/go-playground/locales/en"
-	ut "github.com/go-playground/universal-translator"
-	vr "github.com/go-playground/validator/v10"
-	"github.com/porter-dev/porter/api/server/shared/config/env"
-	"github.com/porter-dev/porter/internal/auth/sessionstore"
-	"github.com/porter-dev/porter/internal/auth/token"
-	"github.com/porter-dev/porter/internal/kubernetes/local"
-	"github.com/porter-dev/porter/internal/notifier/sendgrid"
-	"github.com/porter-dev/porter/internal/oauth"
-	"golang.org/x/oauth2"
-	"gorm.io/gorm"
-
-	"github.com/gorilla/sessions"
-	"github.com/porter-dev/porter/internal/helm"
-	"github.com/porter-dev/porter/internal/helm/loader"
-	"github.com/porter-dev/porter/internal/kubernetes"
-	lr "github.com/porter-dev/porter/internal/logger"
-	notif "github.com/porter-dev/porter/internal/notifier"
-	"github.com/porter-dev/porter/internal/repository"
-	"github.com/porter-dev/porter/internal/validator"
-	"helm.sh/helm/v3/pkg/storage"
-
-	"github.com/porter-dev/porter/internal/analytics"
-)
-
-// TestAgents are the k8s agents used for testing
-type TestAgents struct {
-	HelmAgent             *helm.Agent
-	HelmTestStorageDriver *storage.Storage
-	K8sAgent              *kubernetes.Agent
-}
-
-// AppConfig is the configuration required for creating a new App
-type AppConfig struct {
-	Version    string
-	DB         *gorm.DB
-	Logger     *lr.Logger
-	Repository repository.Repository
-	ServerConf env.ServerConf
-	RedisConf  *env.RedisConf
-	DBConf     env.DBConf
-	CapConf    config.CapConf
-
-	// TestAgents if API is in testing mode
-	TestAgents *TestAgents
-}
-
-// App represents an API instance with handler methods attached, a DB connection
-// and a logger instance
-type App struct {
-	// Server configuration
-	ServerConf env.ServerConf
-
-	// Logger for logging
-	Logger *lr.Logger
-
-	// Repo implements a query repository
-	Repo repository.Repository
-
-	// session store for cookie-based sessions
-	Store sessions.Store
-
-	// agents exposed for testing
-	TestAgents *TestAgents
-
-	// An in-cluster agent if service is running in cluster
-	ProvisionerAgent *kubernetes.Agent
-	IngressAgent     *kubernetes.Agent
-
-	// redis client for redis connection
-	RedisConf *env.RedisConf
-
-	// config for db
-	DBConf env.DBConf
-
-	// config for capabilities
-	Capabilities *AppCapabilities
-
-	// ChartLookupURLs contains an in-memory store of Porter chart names matched with
-	// a repo URL, so that finding a chart does not involve multiple lookups to our
-	// chart repo's index.yaml file
-	ChartLookupURLs map[string]string
-
-	// oauth-specific clients
-	GithubUserConf    *oauth2.Config
-	GithubProjectConf *oauth2.Config
-	GithubAppConf     *oauth.GithubAppConf
-	DOConf            *oauth2.Config
-	GoogleUserConf    *oauth2.Config
-	SlackConf         *oauth2.Config
-
-<<<<<<< HEAD
-	db              *gorm.DB
-	validator       *vr.Validate
-	translator      *ut.Translator
-	tokenConf       *token.TokenGeneratorConf
-	analyticsClient analytics.AnalyticsSegmentClient
-	notifier        notif.UserNotifier
-=======
-	// analytics client for reporting
-	AnalyticsClient analytics.AnalyticsSegmentClient
-
-	db         *gorm.DB
-	validator  *vr.Validate
-	translator *ut.Translator
-	tokenConf  *token.TokenGeneratorConf
->>>>>>> master
-}
-
-type AppCapabilities struct {
-	Version            string `json:"version"`
-	Provisioning       bool   `json:"provisioner"`
-	Github             bool   `json:"github"`
-	BasicLogin         bool   `json:"basic_login"`
-	GithubLogin        bool   `json:"github_login"`
-	GoogleLogin        bool   `json:"google_login"`
-	SlackNotifications bool   `json:"slack_notifs"`
-	Email              bool   `json:"email"`
-	Analytics          bool   `json:"analytics"`
-}
-
-// New returns a new App instance
-func New(conf *AppConfig) (*App, error) {
-	// create a new validator and translator
-	validator := validator.New()
-
-	en := en.New()
-	uni := ut.New(en, en)
-	translator, found := uni.GetTranslator("en")
-
-	if !found {
-		return nil, fmt.Errorf("could not find \"en\" translator")
-	}
-
-	app := &App{
-		Logger:     conf.Logger,
-		Repo:       conf.Repository,
-		ServerConf: conf.ServerConf,
-		RedisConf:  conf.RedisConf,
-		DBConf:     conf.DBConf,
-		TestAgents: conf.TestAgents,
-		Capabilities: &AppCapabilities{
-			Version: conf.Version,
-		},
-		db:         conf.DB,
-		validator:  validator,
-		translator: &translator,
-	}
-
-	// if repository not specified, default to in-memory
-	// if app.Repo == nil {
-	// 	app.Repo = test.NewRepository(true)
-	// }
-
-	// create the session store
-	store, err := sessionstore.NewStore(
-		&sessionstore.NewStoreOpts{
-			SessionRepository: app.Repo.Session(),
-			CookieSecrets:     app.ServerConf.CookieSecrets,
-		},
-	)
-
-	if err != nil {
-		return nil, err
-	}
-
-	app.Store = store
-	sc := conf.ServerConf
-
-	// get the InClusterAgent from either a file-based kubeconfig or the in-cluster agent
-	app.assignProvisionerAgent(&sc)
-	app.assignIngressAgent(&sc)
-
-	// if server config contains OAuth client info, create clients
-	if sc.GithubClientID != "" && sc.GithubClientSecret != "" {
-		app.Capabilities.Github = true
-
-		app.GithubUserConf = oauth.NewGithubClient(&oauth.Config{
-			ClientID:     sc.GithubClientID,
-			ClientSecret: sc.GithubClientSecret,
-			Scopes:       []string{"read:user", "user:email"},
-			BaseURL:      sc.ServerURL,
-		})
-
-		app.GithubProjectConf = oauth.NewGithubClient(&oauth.Config{
-			ClientID:     sc.GithubClientID,
-			ClientSecret: sc.GithubClientSecret,
-			Scopes:       []string{"repo", "read:user", "workflow"},
-			BaseURL:      sc.ServerURL,
-		})
-
-		app.Capabilities.GithubLogin = sc.GithubLoginEnabled
-	}
-
-	if sc.GithubAppClientID != "" &&
-		sc.GithubAppClientSecret != "" &&
-		sc.GithubAppName != "" &&
-		sc.GithubAppWebhookSecret != "" &&
-		sc.GithubAppSecretPath != "" &&
-		sc.GithubAppID != "" {
-		if AppID, err := strconv.ParseInt(sc.GithubAppID, 10, 64); err == nil {
-			app.GithubAppConf = oauth.NewGithubAppClient(&oauth.Config{
-				ClientID:     sc.GithubAppClientID,
-				ClientSecret: sc.GithubAppClientSecret,
-				Scopes:       []string{"read:user"},
-				BaseURL:      sc.ServerURL,
-			}, sc.GithubAppName, sc.GithubAppWebhookSecret, sc.GithubAppSecretPath, AppID)
-		}
-	}
-
-	if sc.GoogleClientID != "" && sc.GoogleClientSecret != "" {
-		app.Capabilities.GoogleLogin = true
-
-		app.GoogleUserConf = oauth.NewGoogleClient(&oauth.Config{
-			ClientID:     sc.GoogleClientID,
-			ClientSecret: sc.GoogleClientSecret,
-			Scopes: []string{
-				"openid",
-				"profile",
-				"email",
-			},
-			BaseURL: sc.ServerURL,
-		})
-	}
-
-	if sc.SlackClientID != "" && sc.SlackClientSecret != "" {
-		app.Capabilities.SlackNotifications = true
-
-		app.SlackConf = oauth.NewSlackClient(&oauth.Config{
-			ClientID:     sc.SlackClientID,
-			ClientSecret: sc.SlackClientSecret,
-			Scopes: []string{
-				"incoming-webhook",
-				"team:read",
-			},
-			BaseURL: sc.ServerURL,
-		})
-	}
-
-	if sc.DOClientID != "" && sc.DOClientSecret != "" {
-		app.DOConf = oauth.NewDigitalOceanClient(&oauth.Config{
-			ClientID:     sc.DOClientID,
-			ClientSecret: sc.DOClientSecret,
-			Scopes:       []string{"read", "write"},
-			BaseURL:      sc.ServerURL,
-		})
-	}
-
-	if sc.SendgridAPIKey != "" {
-		app.Capabilities.Email = true
-
-		sgClient := &sendgrid.Client{
-			APIKey:                  sc.SendgridAPIKey,
-			PWResetTemplateID:       sc.SendgridPWResetTemplateID,
-			PWGHTemplateID:          sc.SendgridPWGHTemplateID,
-			VerifyEmailTemplateID:   sc.SendgridVerifyEmailTemplateID,
-			ProjectInviteTemplateID: sc.SendgridProjectInviteTemplateID,
-			SenderEmail:             sc.SendgridSenderEmail,
-		}
-
-		app.notifier = sendgrid.NewUserNotifier(sgClient)
-	}
-
-	app.Capabilities.Analytics = sc.SegmentClientKey != ""
-	app.Capabilities.BasicLogin = sc.BasicLoginEnabled
-
-	app.tokenConf = &token.TokenGeneratorConf{
-		TokenSecret: conf.ServerConf.TokenGeneratorSecret,
-	}
-
-	newSegmentClient := analytics.InitializeAnalyticsSegmentClient(sc.SegmentClientKey, app.Logger)
-	app.AnalyticsClient = newSegmentClient
-
-	app.updateChartRepoURLs()
-
-	return app, nil
-}
-
-func (app *App) assignProvisionerAgent(sc *env.ServerConf) error {
-	if sc.ProvisionerCluster == "kubeconfig" && sc.SelfKubeconfig != "" {
-		app.Capabilities.Provisioning = true
-
-		agent, err := local.GetSelfAgentFromFileConfig(sc.SelfKubeconfig)
-
-		if err != nil {
-			return fmt.Errorf("could not get in-cluster agent: %v", err)
-		}
-
-		app.ProvisionerAgent = agent
-
-		return nil
-	} else if sc.ProvisionerCluster == "kubeconfig" {
-		return fmt.Errorf(`"kubeconfig" cluster option requires path to kubeconfig`)
-	}
-
-	app.Capabilities.Provisioning = true
-
-	agent, err := kubernetes.GetAgentInClusterConfig()
-
-	if err != nil {
-		return fmt.Errorf("could not get in-cluster agent: %v", err)
-	}
-
-	app.ProvisionerAgent = agent
-
-	return nil
-}
-
-func (app *App) assignIngressAgent(sc *env.ServerConf) error {
-	if sc.IngressCluster == "kubeconfig" && sc.SelfKubeconfig != "" {
-		agent, err := local.GetSelfAgentFromFileConfig(sc.SelfKubeconfig)
-
-		if err != nil {
-			return fmt.Errorf("could not get in-cluster agent: %v", err)
-		}
-
-		app.IngressAgent = agent
-
-		return nil
-	} else if sc.IngressCluster == "kubeconfig" {
-		return fmt.Errorf(`"kubeconfig" cluster option requires path to kubeconfig`)
-	}
-
-	agent, err := kubernetes.GetAgentInClusterConfig()
-
-	if err != nil {
-		return fmt.Errorf("could not get in-cluster agent: %v", err)
-	}
-
-	app.IngressAgent = agent
-
-	return nil
-}
-
-func (app *App) getTokenFromRequest(r *http.Request) *token.Token {
-	reqToken := r.Header.Get("Authorization")
-
-	splitToken := strings.Split(reqToken, "Bearer")
-
-	if len(splitToken) != 2 {
-		return nil
-	}
-
-	reqToken = strings.TrimSpace(splitToken[1])
-
-	tok, err := token.GetTokenFromEncoded(reqToken, app.tokenConf)
-
-	if err != nil {
-		return nil
-	}
-
-	return tok
-}
-
-func (app *App) updateChartRepoURLs() {
-	newCharts := make(map[string]string)
-
-	for _, chartRepo := range []string{
-		app.ServerConf.DefaultApplicationHelmRepoURL,
-		app.ServerConf.DefaultAddonHelmRepoURL,
-	} {
-		indexFile, err := loader.LoadRepoIndexPublic(chartRepo)
-
-		if err != nil {
-			continue
-		}
-
-		for chartName, _ := range indexFile.Entries {
-			newCharts[chartName] = chartRepo
-		}
-	}
-
-	app.ChartLookupURLs = newCharts
-}

+ 0 - 15
server/api/capability_handler.go

@@ -1,15 +0,0 @@
-package api
-
-import (
-	"encoding/json"
-	"net/http"
-)
-
-// HandleGetCapabilities gets the capabilities of the server
-func (app *App) HandleGetCapabilities(w http.ResponseWriter, r *http.Request) {
-	if err := json.NewEncoder(w).Encode(app.Capabilities); err != nil {
-		app.handleErrorFormDecoding(err, ErrK8sDecode, w)
-		return
-
-	}
-}

+ 0 - 456
server/api/cluster_handler.go

@@ -1,456 +0,0 @@
-package api
-
-import (
-	"encoding/json"
-	"net/http"
-	"strconv"
-
-	"github.com/go-chi/chi"
-	"github.com/porter-dev/porter/internal/analytics"
-	"github.com/porter-dev/porter/internal/forms"
-	"github.com/porter-dev/porter/internal/kubernetes"
-	"github.com/porter-dev/porter/internal/kubernetes/domain"
-	"github.com/porter-dev/porter/internal/models"
-)
-
-// HandleCreateProjectCluster creates a new cluster
-func (app *App) HandleCreateProjectCluster(w http.ResponseWriter, r *http.Request) {
-	projID, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
-	// userID, err := app.getUserIDFromRequest(r)
-
-	if err != nil || projID == 0 {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	form := &forms.CreateClusterForm{
-		ProjectID: uint(projID),
-	}
-
-	// decode from JSON to form value
-	if err := json.NewDecoder(r.Body).Decode(form); err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	// validate the form
-	if err := app.validator.Struct(form); err != nil {
-		app.handleErrorFormValidation(err, ErrProjectValidateFields, w)
-		return
-	}
-
-	// convert the form to a registry
-	cluster, err := form.ToCluster()
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	// handle write to the database
-	cluster, err = app.Repo.Cluster().CreateCluster(cluster)
-
-	if err != nil {
-		app.handleErrorDataWrite(err, w)
-		return
-	}
-
-	app.Logger.Info().Msgf("New cluster created: %d", cluster.ID)
-
-	w.WriteHeader(http.StatusCreated)
-
-	clusterExt := cluster.Externalize()
-
-	if err := json.NewEncoder(w).Encode(clusterExt); err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-}
-
-// HandleReadProjectCluster reads a cluster by id
-func (app *App) HandleReadProjectCluster(w http.ResponseWriter, r *http.Request) {
-	id, err := strconv.ParseUint(chi.URLParam(r, "cluster_id"), 0, 64)
-
-	if err != nil || id == 0 {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	cluster, err := app.Repo.Cluster().ReadCluster(uint(id))
-
-	if err != nil {
-		app.handleErrorRead(err, ErrProjectDataRead, w)
-		return
-	}
-
-	clusterExt := cluster.DetailedExternalize()
-
-	form := &forms.K8sForm{
-		OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
-			Repo:              app.Repo,
-			DigitalOceanOAuth: app.DOConf,
-			Cluster:           cluster,
-		},
-	}
-
-	var agent *kubernetes.Agent
-
-	if app.ServerConf.IsTesting {
-		agent = app.TestAgents.K8sAgent
-	} else {
-		agent, _ = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
-	}
-
-	endpoint, found, ingressErr := domain.GetNGINXIngressServiceIP(agent.Clientset)
-
-	if found {
-		clusterExt.IngressIP = endpoint
-	}
-
-	if !found && ingressErr != nil {
-		clusterExt.IngressError = kubernetes.CatchK8sConnectionError(ingressErr).Externalize()
-	}
-
-	w.WriteHeader(http.StatusOK)
-
-	if err := json.NewEncoder(w).Encode(clusterExt); err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-}
-
-// HandleListProjectClusters returns a list of clusters that have linked Integrations.
-func (app *App) HandleListProjectClusters(w http.ResponseWriter, r *http.Request) {
-	projID, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
-
-	if err != nil || projID == 0 {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	clusters, err := app.Repo.Cluster().ListClustersByProjectID(uint(projID))
-
-	if err != nil {
-		app.handleErrorRead(err, ErrProjectDataRead, w)
-		return
-	}
-
-	extClusters := make([]*models.ClusterExternal, 0)
-
-	for _, cluster := range clusters {
-		extClusters = append(extClusters, cluster.Externalize())
-	}
-
-	w.WriteHeader(http.StatusOK)
-
-	if err := json.NewEncoder(w).Encode(extClusters); err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-}
-
-// HandleUpdateProjectCluster updates a project's cluster
-func (app *App) HandleUpdateProjectCluster(w http.ResponseWriter, r *http.Request) {
-	projID, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
-
-	if err != nil || projID == 0 {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	clusterID, err := strconv.ParseUint(chi.URLParam(r, "cluster_id"), 0, 64)
-
-	if err != nil || clusterID == 0 {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	form := &forms.UpdateClusterForm{
-		ID: uint(clusterID),
-	}
-
-	// decode from JSON to form value
-	if err := json.NewDecoder(r.Body).Decode(form); err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	// validate the form
-	if err := app.validator.Struct(form); err != nil {
-		app.handleErrorFormValidation(err, ErrProjectValidateFields, w)
-		return
-	}
-
-	// convert the form to a registry
-	cluster, err := form.ToCluster(app.Repo.Cluster())
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	// handle write to the database
-	cluster, err = app.Repo.Cluster().UpdateCluster(cluster)
-
-	if err != nil {
-		app.handleErrorDataWrite(err, w)
-		return
-	}
-
-	w.WriteHeader(http.StatusOK)
-
-	clusterExt := cluster.Externalize()
-
-	if err := json.NewEncoder(w).Encode(clusterExt); err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-}
-
-// HandleDeleteProjectCluster handles the deletion of a Cluster via the cluster ID
-func (app *App) HandleDeleteProjectCluster(w http.ResponseWriter, r *http.Request) {
-	id, err := strconv.ParseUint(chi.URLParam(r, "cluster_id"), 0, 64)
-
-	if err != nil || id == 0 {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	cluster, err := app.Repo.Cluster().ReadCluster(uint(id))
-
-	if err != nil {
-		app.handleErrorRead(err, ErrProjectDataRead, w)
-		return
-	}
-
-	err = app.Repo.Cluster().DeleteCluster(cluster)
-
-	if err != nil {
-		app.handleErrorRead(err, ErrProjectDataRead, w)
-		return
-	}
-
-	w.WriteHeader(http.StatusOK)
-}
-
-// HandleCreateProjectClusterCandidates handles the creation of ClusterCandidates using
-// a kubeconfig and a project id
-func (app *App) HandleCreateProjectClusterCandidates(w http.ResponseWriter, r *http.Request) {
-	projID, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
-
-	if err != nil || projID == 0 {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	form := &forms.CreateClusterCandidatesForm{
-		ProjectID: uint(projID),
-	}
-
-	// decode from JSON to form value
-	if err := json.NewDecoder(r.Body).Decode(form); err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	// validate the form
-	if err := app.validator.Struct(form); err != nil {
-		app.handleErrorFormValidation(err, ErrProjectValidateFields, w)
-		return
-	}
-
-	// convert the form to a ClusterCandidate
-	ccs, err := form.ToClusterCandidates(app.ServerConf.IsLocal)
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	extClusters := make([]*models.ClusterCandidateExternal, 0)
-
-	userID, _ := app.getUserIDFromRequest(r)
-
-	for _, cc := range ccs {
-		// handle write to the database
-		cc, err = app.Repo.Cluster().CreateClusterCandidate(cc)
-
-		if err != nil {
-			app.handleErrorDataWrite(err, w)
-			return
-		}
-
-		app.AnalyticsClient.Track(analytics.ClusterConnectionStartTrack(
-			&analytics.ClusterConnectionStartTrackOpts{
-				ProjectScopedTrackOpts: analytics.GetProjectScopedTrackOpts(userID, uint(projID)),
-				ClusterCandidateID:     cc.ID,
-			},
-		))
-
-		app.Logger.Info().Msgf("New cluster candidate created: %d", cc.ID)
-
-		// if the ClusterCandidate does not have any actions to perform, create the Cluster
-		// automatically
-		if len(cc.Resolvers) == 0 {
-			// we query the repo again to get the decrypted version of the cluster candidate
-			cc, err = app.Repo.Cluster().ReadClusterCandidate(cc.ID)
-
-			if err != nil {
-				app.handleErrorDataRead(err, w)
-				return
-			}
-
-			clusterForm := &forms.ResolveClusterForm{
-				Resolver:           &models.ClusterResolverAll{},
-				ClusterCandidateID: cc.ID,
-				ProjectID:          uint(projID),
-				UserID:             userID,
-			}
-
-			err := clusterForm.ResolveIntegration(app.Repo)
-
-			if err != nil {
-				app.handleErrorDataWrite(err, w)
-				return
-			}
-
-			cluster, err := clusterForm.ResolveCluster(app.Repo)
-
-			if err != nil {
-				app.handleErrorDataWrite(err, w)
-				return
-			}
-
-			cc, err = app.Repo.Cluster().UpdateClusterCandidateCreatedClusterID(cc.ID, cluster.ID)
-
-			if err != nil {
-				app.handleErrorDataWrite(err, w)
-				return
-			}
-
-			app.Logger.Info().Msgf("New cluster created: %d", cluster.ID)
-
-			app.AnalyticsClient.Track(analytics.ClusterConnectionSuccessTrack(
-				&analytics.ClusterConnectionSuccessTrackOpts{
-					ClusterScopedTrackOpts: analytics.GetClusterScopedTrackOpts(userID, uint(projID), cluster.ID),
-					ClusterCandidateID:     cc.ID,
-				},
-			))
-		}
-
-		extClusters = append(extClusters, cc.Externalize())
-	}
-
-	w.WriteHeader(http.StatusCreated)
-
-	if err := json.NewEncoder(w).Encode(extClusters); err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-}
-
-// HandleListProjectClusterCandidates returns a list of externalized ClusterCandidates
-// ([]models.ClusterCandidateExternal) based on a project ID
-func (app *App) HandleListProjectClusterCandidates(w http.ResponseWriter, r *http.Request) {
-	projID, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
-
-	if err != nil || projID == 0 {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	ccs, err := app.Repo.Cluster().ListClusterCandidatesByProjectID(uint(projID))
-
-	if err != nil {
-		app.handleErrorRead(err, ErrProjectDataRead, w)
-		return
-	}
-
-	extCCs := make([]*models.ClusterCandidateExternal, 0)
-
-	for _, cc := range ccs {
-		extCCs = append(extCCs, cc.Externalize())
-	}
-
-	w.WriteHeader(http.StatusOK)
-
-	if err := json.NewEncoder(w).Encode(extCCs); err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-}
-
-// HandleResolveClusterCandidate accepts a list of resolving objects (ClusterResolver)
-// for a given ClusterCandidate, which "resolves" that ClusterCandidate and creates a
-// Cluster for a specific project
-func (app *App) HandleResolveClusterCandidate(w http.ResponseWriter, r *http.Request) {
-	projID, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
-
-	if err != nil || projID == 0 {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	candID, err := strconv.ParseUint(chi.URLParam(r, "candidate_id"), 0, 64)
-
-	if err != nil || projID == 0 {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	userID, _ := app.getUserIDFromRequest(r)
-
-	// decode actions from request
-	resolver := &models.ClusterResolverAll{}
-
-	if err := json.NewDecoder(r.Body).Decode(resolver); err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	clusterResolver := &forms.ResolveClusterForm{
-		Resolver:           resolver,
-		ClusterCandidateID: uint(candID),
-		ProjectID:          uint(projID),
-		UserID:             userID,
-	}
-
-	err = clusterResolver.ResolveIntegration(app.Repo)
-
-	if err != nil {
-		app.handleErrorDataWrite(err, w)
-		return
-	}
-
-	cluster, err := clusterResolver.ResolveCluster(app.Repo)
-
-	if err != nil {
-		app.handleErrorDataWrite(err, w)
-		return
-	}
-
-	_, err = app.Repo.Cluster().UpdateClusterCandidateCreatedClusterID(uint(candID), cluster.ID)
-
-	if err != nil {
-		app.handleErrorDataWrite(err, w)
-		return
-	}
-
-	app.Logger.Info().Msgf("New cluster created: %d", cluster.ID)
-
-	app.AnalyticsClient.Track(analytics.ClusterConnectionSuccessTrack(
-		&analytics.ClusterConnectionSuccessTrackOpts{
-			ClusterScopedTrackOpts: analytics.GetClusterScopedTrackOpts(userID, uint(projID), cluster.ID),
-			ClusterCandidateID:     uint(candID),
-		},
-	))
-
-	clusterExt := cluster.Externalize()
-
-	w.WriteHeader(http.StatusCreated)
-
-	if err := json.NewEncoder(w).Encode(clusterExt); err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-}

+ 0 - 480
server/api/cluster_handler_test.go

@@ -1,480 +0,0 @@
-package api_test
-
-import (
-	"github.com/porter-dev/porter/internal/forms"
-	"github.com/porter-dev/porter/internal/kubernetes/fixtures"
-	"github.com/porter-dev/porter/internal/models"
-)
-
-// import (
-// 	"encoding/json"
-// 	"net/http"
-// 	"net/http/httptest"
-// 	"strings"
-// 	"testing"
-
-// 	"github.com/porter-dev/porter/internal/kubernetes/fixtures"
-// 	"github.com/porter-dev/porter/internal/models/integrations"
-// 	"gorm.io/gorm"
-
-// 	"github.com/go-test/deep"
-// 	"github.com/porter-dev/porter/internal/forms"
-// 	"github.com/porter-dev/porter/internal/models"
-// )
-
-// // ------------------------- TEST TYPES AND MAIN LOOP ------------------------- //
-
-// type clusterTest struct {
-// 	initializers []func(t *tester)
-// 	msg          string
-// 	method       string
-// 	endpoint     string
-// 	body         string
-// 	expStatus    int
-// 	expBody      string
-// 	useCookie    bool
-// 	validators   []func(c *clusterTest, tester *tester, t *testing.T)
-// }
-
-// func testClusterRequests(t *testing.T, tests []*clusterTest, canQuery bool) {
-// 	for _, c := range tests {
-// 		// create a new tester
-// 		tester := newTester(canQuery)
-
-// 		// if there's an initializer, call it
-// 		for _, init := range c.initializers {
-// 			init(tester)
-// 		}
-
-// 		req, err := http.NewRequest(
-// 			c.method,
-// 			c.endpoint,
-// 			strings.NewReader(c.body),
-// 		)
-
-// 		tester.req = req
-
-// 		if c.useCookie {
-// 			req.AddCookie(tester.cookie)
-// 		}
-
-// 		if err != nil {
-// 			t.Fatal(err)
-// 		}
-
-// 		tester.execute()
-// 		rr := tester.rr
-
-// 		// first, check that the status matches
-// 		if status := rr.Code; status != c.expStatus {
-// 			t.Errorf("%s, handler returned wrong status code: got %v want %v",
-// 				c.msg, status, c.expStatus)
-// 		}
-
-// 		// if there's a validator, call it
-// 		for _, validate := range c.validators {
-// 			validate(c, tester, t)
-// 		}
-// 	}
-// }
-
-// // ------------------------- TEST FIXTURES AND FUNCTIONS  ------------------------- //
-
-// var createClusterTests = []*clusterTest{
-// 	{
-// 		initializers: []func(t *tester){
-// 			initUserDefault,
-// 			initProject,
-// 			initAWSIntegration,
-// 		},
-// 		msg:       "Create cluster",
-// 		method:    "POST",
-// 		endpoint:  "/api/projects/1/clusters",
-// 		body:      `{"name":"cluster-test","server":"https://10.10.10.10:6443","aws_integration_id":1}`,
-// 		expStatus: http.StatusCreated,
-// 		expBody:   `{"id":1,"project_id":1,"name":"cluster-test","server":"https://10.10.10.10:6443","service":"eks"}`,
-// 		useCookie: true,
-// 		validators: []func(c *clusterTest, tester *tester, t *testing.T){
-// 			projectClusterBodyValidator,
-// 		},
-// 	},
-// }
-
-// func TestHandleCreateCluster(t *testing.T) {
-// 	testClusterRequests(t, createClusterTests, true)
-// }
-
-// var readProjectClusterTest = []*clusterTest{
-// 	{
-// 		initializers: []func(t *tester){
-// 			initUserDefault,
-// 			initProject,
-// 			initProjectClusterDefault,
-// 		},
-// 		msg:       "Read project cluster",
-// 		method:    "GET",
-// 		endpoint:  "/api/projects/1/clusters/1",
-// 		body:      ``,
-// 		expStatus: http.StatusOK,
-// 		expBody:   `{"id":1,"project_id":1,"name":"cluster-test","server":"https://10.10.10.10","service":"kube"}`,
-// 		useCookie: true,
-// 		validators: []func(c *clusterTest, tester *tester, t *testing.T){
-// 			projectClusterBodyValidator,
-// 		},
-// 	},
-// }
-
-// func TestHandleReadProjectCluster(t *testing.T) {
-// 	testClusterRequests(t, readProjectClusterTest, true)
-// }
-
-// var listProjectClustersTest = []*clusterTest{
-// 	{
-// 		initializers: []func(t *tester){
-// 			initUserDefault,
-// 			initProject,
-// 			initProjectClusterDefault,
-// 		},
-// 		msg:       "List project clusters",
-// 		method:    "GET",
-// 		endpoint:  "/api/projects/1/clusters",
-// 		body:      ``,
-// 		expStatus: http.StatusOK,
-// 		expBody:   `[{"id":1,"project_id":1,"name":"cluster-test","server":"https://10.10.10.10","service":"kube"}]`,
-// 		useCookie: true,
-// 		validators: []func(c *clusterTest, tester *tester, t *testing.T){
-// 			projectClustersBodyValidator,
-// 		},
-// 	},
-// }
-
-// func TestHandleListProjectClusters(t *testing.T) {
-// 	testClusterRequests(t, listProjectClustersTest, true)
-// }
-
-// var updateClusterTests = []*clusterTest{
-// 	{
-// 		initializers: []func(t *tester){
-// 			initUserDefault,
-// 			initProject,
-// 			initProjectClusterDefault,
-// 		},
-// 		msg:       "Update cluster name",
-// 		method:    "POST",
-// 		endpoint:  "/api/projects/1/clusters/1",
-// 		body:      `{"name":"cluster-new-name"}`,
-// 		expStatus: http.StatusOK,
-// 		expBody:   `{"id":1,"project_id":1,"name":"cluster-new-name","server":"https://10.10.10.10","service":"kube"}`,
-// 		useCookie: true,
-// 		validators: []func(c *clusterTest, tester *tester, t *testing.T){
-// 			projectClusterBodyValidator,
-// 		},
-// 	},
-// }
-
-// func TestHandleUpdateCluster(t *testing.T) {
-// 	testClusterRequests(t, updateClusterTests, true)
-// }
-
-// var deleteClusterTests = []*clusterTest{
-// 	{
-// 		initializers: []func(t *tester){
-// 			initUserDefault,
-// 			initProject,
-// 			initProjectClusterDefault,
-// 		},
-// 		msg:       "Delete cluster",
-// 		method:    "DELETE",
-// 		endpoint:  "/api/projects/1/clusters/1",
-// 		body:      ``,
-// 		expStatus: http.StatusOK,
-// 		expBody:   ``,
-// 		useCookie: true,
-// 		validators: []func(c *clusterTest, tester *tester, t *testing.T){
-// 			func(c *clusterTest, tester *tester, t *testing.T) {
-// 				req, err := http.NewRequest(
-// 					"GET",
-// 					"/api/projects/1/clusters/1",
-// 					strings.NewReader(""),
-// 				)
-
-// 				req.AddCookie(tester.cookie)
-
-// 				if err != nil {
-// 					t.Fatal(err)
-// 				}
-
-// 				rr2 := httptest.NewRecorder()
-
-// 				tester.router.ServeHTTP(rr2, req)
-
-// 				if status := rr2.Code; status != 403 {
-// 					t.Errorf("DELETE cluster validation, handler returned wrong status code: got %v want %v",
-// 						status, 403)
-// 				}
-// 			},
-// 		},
-// 	},
-// }
-
-// func TestHandleDeleteCluster(t *testing.T) {
-// 	testClusterRequests(t, deleteClusterTests, true)
-// }
-
-// var createProjectClusterCandidatesTests = []*clusterTest{
-// 	{
-// 		initializers: []func(t *tester){
-// 			initUserDefault,
-// 			initProject,
-// 		},
-// 		msg:       "Create project cluster candidate w/ no actions -- should create SA by default",
-// 		method:    "POST",
-// 		endpoint:  "/api/projects/1/clusters/candidates",
-// 		body:      `{"kubeconfig":"` + OIDCAuthWithDataForJSON + `"}`,
-// 		expStatus: http.StatusCreated,
-// 		expBody:   `[{"id":1,"resolvers":[],"created_cluster_id":1,"project_id":1,"context_name":"context-test","name":"cluster-test","server":"https://10.10.10.10"}]`,
-// 		useCookie: true,
-// 		validators: []func(c *clusterTest, tester *tester, t *testing.T){
-// 			projectClusterCandidateBodyValidator,
-// 			// check that Cluster was created by default
-// 			func(c *clusterTest, tester *tester, t *testing.T) {
-// 				clusters, err := tester.repo.Cluster().ListClustersByProjectID(1)
-
-// 				if err != nil {
-// 					t.Fatalf("%v\n", err)
-// 				}
-
-// 				if len(clusters) != 1 {
-// 					t.Fatal("Expected cluster to be created by default, but does not exist\n")
-// 				}
-
-// 				gotCluster := clusters[0]
-// 				gotCluster.Model = gorm.Model{}
-
-// 				expCluster := &models.Cluster{
-// 					AuthMechanism:            models.OIDC,
-// 					ProjectID:                1,
-// 					Name:                     "cluster-test",
-// 					Server:                   "https://10.10.10.10",
-// 					OIDCIntegrationID:        1,
-// 					TokenCache:               integrations.ClusterTokenCache{},
-// 					CertificateAuthorityData: []byte("-----BEGIN CER"),
-// 				}
-
-// 				if diff := deep.Equal(gotCluster, expCluster); diff != nil {
-// 					t.Errorf("handler returned wrong body:\n")
-// 					t.Error(diff)
-// 				}
-// 			},
-// 		},
-// 	},
-// 	{
-// 		initializers: []func(t *tester){
-// 			initUserDefault,
-// 			initProject,
-// 		},
-// 		msg:       "Create project SA candidate",
-// 		method:    "POST",
-// 		endpoint:  "/api/projects/1/clusters/candidates",
-// 		body:      `{"kubeconfig":"` + OIDCAuthWithoutDataForJSON + `"}`,
-// 		expStatus: http.StatusCreated,
-// 		expBody:   `[{"id":1,"resolvers":[{"name":"upload-oidc-idp-issuer-ca-data","data":{"filename":"/fake/path/to/ca.pem"},"docs":"https://github.com/porter-dev/porter","resolved":false,"fields":"oidc_idp_issuer_ca_data"}],"created_cluster_id":0,"project_id":1,"context_name":"context-test","name":"cluster-test","server":"https://10.10.10.10"}]`,
-// 		useCookie: true,
-// 		validators: []func(c *clusterTest, tester *tester, t *testing.T){
-// 			projectClusterCandidateBodyValidator,
-// 		},
-// 	},
-// }
-
-// func TestHandleCreateProjectClusterCandidate(t *testing.T) {
-// 	testClusterRequests(t, createProjectClusterCandidatesTests, true)
-// }
-
-// var listProjectClusterCandidatesTests = []*clusterTest{
-// 	{
-// 		initializers: []func(t *tester){
-// 			initUserDefault,
-// 			initProject,
-// 			initProjectClusterCandidate,
-// 		},
-// 		msg:       "List project cluster candidates",
-// 		method:    "GET",
-// 		endpoint:  "/api/projects/1/clusters/candidates",
-// 		body:      ``,
-// 		expStatus: http.StatusOK,
-// 		expBody:   `[{"id":1,"resolvers":[{"name":"upload-oidc-idp-issuer-ca-data","data":{"filename":"/fake/path/to/ca.pem"},"docs":"https://github.com/porter-dev/porter","resolved":false,"fields":"oidc_idp_issuer_ca_data"}],"created_cluster_id":0,"project_id":1,"context_name":"context-test","name":"cluster-test","server":"https://10.10.10.10"}]`,
-// 		useCookie: true,
-// 		validators: []func(c *clusterTest, tester *tester, t *testing.T){
-// 			projectClusterCandidateBodyValidator,
-// 		},
-// 	},
-// }
-
-// func TestHandleListProjectClusterCandidates(t *testing.T) {
-// 	testClusterRequests(t, listProjectClusterCandidatesTests, true)
-// }
-
-// var resolveProjectClusterCandidatesTests = []*clusterTest{
-// 	{
-// 		initializers: []func(t *tester){
-// 			initUserDefault,
-// 			initProject,
-// 			initProjectClusterCandidate,
-// 		},
-// 		msg:       "Resolve project cluster candidate",
-// 		method:    "POST",
-// 		endpoint:  "/api/projects/1/clusters/candidates/1/resolve",
-// 		body:      `{"oidc_idp_issuer_ca_data": "LS0tLS1CRUdJTiBDRVJ="}`,
-// 		expStatus: http.StatusCreated,
-// 		expBody:   `{"id":1,"project_id":1,"name":"cluster-test","server":"https://10.10.10.10","service":"kube"}`,
-// 		useCookie: true,
-// 		validators: []func(c *clusterTest, tester *tester, t *testing.T){
-// 			projectClusterBodyValidator,
-// 		},
-// 	},
-// }
-
-// func TestHandleResolveProjectClusterCandidate(t *testing.T) {
-// 	testClusterRequests(t, resolveProjectClusterCandidatesTests, true)
-// }
-
-// // ------------------------- INITIALIZERS AND VALIDATORS ------------------------- //
-
-// func initProjectClusterCandidate(tester *tester) {
-// 	proj, _ := tester.repo.Project().ReadProject(1)
-
-// 	form := &forms.CreateClusterCandidatesForm{
-// 		ProjectID:  proj.ID,
-// 		Kubeconfig: fixtures.OIDCAuthWithoutData,
-// 	}
-
-// 	// convert the form to a ServiceAccountCandidate
-// 	ccs, _ := form.ToClusterCandidates(false)
-
-// 	for _, cc := range ccs {
-// 		tester.repo.Cluster().CreateClusterCandidate(cc)
-// 	}
-// }
-
-func initProjectClusterDefault(tester *tester) {
-	proj, _ := tester.repo.Project().ReadProject(1)
-
-	form := &forms.CreateClusterCandidatesForm{
-		ProjectID:  proj.ID,
-		Kubeconfig: fixtures.OIDCAuthWithData,
-	}
-
-	// convert the form to a ServiceAccountCandidate
-	ccs, _ := form.ToClusterCandidates(false)
-
-	for _, cc := range ccs {
-		tester.repo.Cluster().CreateClusterCandidate(cc)
-	}
-
-	clusterForm := forms.ResolveClusterForm{
-		Resolver:           &models.ClusterResolverAll{},
-		ClusterCandidateID: 1,
-		ProjectID:          1,
-		UserID:             1,
-	}
-
-	clusterForm.ResolveIntegration(tester.repo)
-	clusterForm.ResolveCluster(tester.repo)
-}
-
-// func projectClusterCandidateBodyValidator(c *clusterTest, tester *tester, t *testing.T) {
-// 	gotBody := make([]*models.ClusterCandidateExternal, 0)
-// 	expBody := make([]*models.ClusterCandidateExternal, 0)
-
-// 	json.Unmarshal(tester.rr.Body.Bytes(), &gotBody)
-// 	json.Unmarshal([]byte(c.expBody), &expBody)
-
-// 	if diff := deep.Equal(gotBody, expBody); diff != nil {
-// 		t.Errorf("handler returned wrong body:\n")
-// 		t.Error(diff)
-// 	}
-// }
-
-// func projectClusterBodyValidator(c *clusterTest, tester *tester, t *testing.T) {
-// 	gotBody := &models.ClusterExternal{}
-// 	expBody := &models.ClusterExternal{}
-
-// 	json.Unmarshal(tester.rr.Body.Bytes(), gotBody)
-// 	json.Unmarshal([]byte(c.expBody), expBody)
-
-// 	if diff := deep.Equal(gotBody, expBody); diff != nil {
-// 		t.Errorf("handler returned wrong body:\n")
-// 		t.Error(diff)
-// 	}
-// }
-
-// func projectClustersBodyValidator(c *clusterTest, tester *tester, t *testing.T) {
-// 	gotBody := make([]*models.ClusterExternal, 0)
-// 	expBody := make([]*models.ClusterExternal, 0)
-
-// 	json.Unmarshal(tester.rr.Body.Bytes(), &gotBody)
-// 	json.Unmarshal([]byte(c.expBody), &expBody)
-
-// 	if diff := deep.Equal(gotBody, expBody); diff != nil {
-// 		t.Errorf("handler returned wrong body:\n")
-// 		t.Error(diff)
-// 	}
-// }
-
-// const OIDCAuthWithDataForJSON string = `apiVersion: v1\nclusters:\n- cluster:\n    server: https://10.10.10.10\n    certificate-authority-data: LS0tLS1CRUdJTiBDRVJ=\n  name: cluster-test\ncontexts:\n- context:\n    cluster: cluster-test\n    user: test-admin\n  name: context-test\ncurrent-context: context-test\nkind: Config\npreferences: {}\nusers:\n- name: test-admin\n  user:\n    auth-provider:\n      config:\n        client-id: porter-api\n        id-token: token\n        idp-issuer-url: https://10.10.10.10\n        idp-certificate-authority-data: LS0tLS1CRUdJTiBDRVJ=\n      name: oidc`
-
-// const OIDCAuthWithoutDataForJSON string = `apiVersion: v1\nclusters:\n- cluster:\n    server: https://10.10.10.10\n    certificate-authority-data: LS0tLS1CRUdJTiBDRVJ=\n  name: cluster-test\ncontexts:\n- context:\n    cluster: cluster-test\n    user: test-admin\n  name: context-test\ncurrent-context: context-test\nkind: Config\npreferences: {}\nusers:\n- name: test-admin\n  user:\n    auth-provider:\n      config:\n        client-id: porter-api\n        id-token: token\n        idp-issuer-url: https://10.10.10.10\n        idp-certificate-authority: /fake/path/to/ca.pem\n      name: oidc`
-
-// const OIDCAuthWithoutData string = `
-// apiVersion: v1
-// clusters:
-// - cluster:
-//     server: https://10.10.10.10
-//     certificate-authority-data: LS0tLS1CRUdJTiBDRVJ=
-//   name: cluster-test
-// contexts:
-// - context:
-//     cluster: cluster-test
-//     user: test-admin
-//   name: context-test
-// current-context: context-test
-// kind: Config
-// preferences: {}
-// users:
-// - name: test-admin
-//   user:
-//     auth-provider:
-//       config:
-//         client-id: porter-api
-//         id-token: token
-//         idp-issuer-url: https://10.10.10.10
-//         idp-certificate-authority: /fake/path/to/ca.pem
-//       name: oidc
-// `
-
-// const OIDCAuthWithData string = `
-// apiVersion: v1
-// clusters:
-// - cluster:
-//     server: https://10.10.10.10
-//     certificate-authority-data: LS0tLS1CRUdJTiBDRVJ=
-//   name: cluster-test
-// contexts:
-// - context:
-//     cluster: cluster-test
-//     user: test-admin
-//   name: context-test
-// current-context: context-test
-// kind: Config
-// preferences: {}
-// users:
-// - name: test-admin
-//   user:
-//     auth-provider:
-//       config:
-//         client-id: porter-api
-//         id-token: token
-//         idp-issuer-url: https://10.10.10.10
-//         idp-certificate-authority-data: LS0tLS1CRUdJTiBDRVJ=
-//       name: oidc
-// `

+ 0 - 470
server/api/deploy_handler.go

@@ -1,470 +0,0 @@
-package api
-
-import (
-	"encoding/json"
-	"fmt"
-	"net/http"
-	"net/url"
-	"strconv"
-	"strings"
-
-	"gorm.io/gorm"
-
-	"github.com/go-chi/chi"
-	"github.com/porter-dev/porter/internal/analytics"
-	"github.com/porter-dev/porter/internal/forms"
-	"github.com/porter-dev/porter/internal/helm"
-	"github.com/porter-dev/porter/internal/helm/loader"
-	"github.com/porter-dev/porter/internal/integrations/ci/actions"
-	"github.com/porter-dev/porter/internal/models"
-	"github.com/porter-dev/porter/internal/oauth"
-	"github.com/porter-dev/porter/internal/repository"
-	"gopkg.in/yaml.v2"
-)
-
-// HandleDeployTemplate triggers a chart deployment from a template
-func (app *App) HandleDeployTemplate(w http.ResponseWriter, r *http.Request) {
-	projID, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
-	userID, err := app.getUserIDFromRequest(r)
-	flowID := oauth.CreateRandomState()
-
-	if err != nil || projID == 0 {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	name := chi.URLParam(r, "name")
-	version := chi.URLParam(r, "version")
-
-	// if version passed as latest, pass empty string to loader to get latest
-	if version == "latest" {
-		version = ""
-	}
-
-	getChartForm := &forms.ChartForm{
-		Name:    name,
-		Version: version,
-		RepoURL: app.ServerConf.DefaultApplicationHelmRepoURL,
-	}
-
-	// if a repo_url is passed as query param, it will be populated
-	vals, err := url.ParseQuery(r.URL.RawQuery)
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
-		return
-	}
-
-	clusterID, err := strconv.ParseUint(vals["cluster_id"][0], 10, 64)
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
-		return
-	}
-
-	app.AnalyticsClient.Track(analytics.ApplicationLaunchStartTrack(
-		&analytics.ApplicationLaunchStartTrackOpts{
-			ClusterScopedTrackOpts: analytics.GetClusterScopedTrackOpts(userID, uint(projID), uint(clusterID)),
-			FlowID:                 flowID,
-		},
-	))
-
-	getChartForm.PopulateRepoURLFromQueryParams(vals)
-
-	chart, err := loader.LoadChartPublic(getChartForm.RepoURL, getChartForm.Name, getChartForm.Version)
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
-		return
-	}
-
-	form := &forms.InstallChartTemplateForm{
-		ReleaseForm: &forms.ReleaseForm{
-			Form: &helm.Form{
-				Repo:              app.Repo,
-				DigitalOceanOAuth: app.DOConf,
-			},
-		},
-		ChartTemplateForm: &forms.ChartTemplateForm{},
-	}
-
-	form.ReleaseForm.PopulateHelmOptionsFromQueryParams(
-		vals,
-		app.Repo.Cluster(),
-	)
-
-	if err := json.NewDecoder(r.Body).Decode(form); err != nil {
-		app.handleErrorFormDecoding(err, ErrUserDecode, w)
-		return
-	}
-
-	agent, err := app.getAgentFromReleaseForm(
-		w,
-		r,
-		form.ReleaseForm,
-	)
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrUserDecode, w)
-		return
-	}
-
-	registries, err := app.Repo.Registry().ListRegistriesByProjectID(uint(projID))
-
-	if err != nil {
-		app.handleErrorDataRead(err, w)
-		return
-	}
-
-	conf := &helm.InstallChartConfig{
-		Chart:      chart,
-		Name:       form.ChartTemplateForm.Name,
-		Namespace:  form.ReleaseForm.Form.Namespace,
-		Values:     form.ChartTemplateForm.FormValues,
-		Cluster:    form.ReleaseForm.Cluster,
-		Repo:       app.Repo,
-		Registries: registries,
-	}
-
-	rel, err := agent.InstallChart(conf, app.DOConf)
-
-	if err != nil {
-		app.sendExternalError(err, http.StatusInternalServerError, HTTPError{
-			Code:   ErrReleaseDeploy,
-			Errors: []string{"error installing a new chart: " + err.Error()},
-		}, w)
-
-		return
-	}
-
-	token, err := repository.GenerateRandomBytes(16)
-
-	if err != nil {
-		app.handleErrorInternal(err, w)
-		return
-	}
-
-	// create release with webhook token in db
-	image, ok := rel.Config["image"].(map[string]interface{})
-	if !ok {
-		app.handleErrorInternal(fmt.Errorf("Could not find field image in config"), w)
-		return
-	}
-
-	repository := image["repository"]
-	repoStr, ok := repository.(string)
-
-	if !ok {
-		app.handleErrorInternal(fmt.Errorf("Could not find field repository in config"), w)
-		return
-	}
-
-	release := &models.Release{
-		ClusterID:    form.ReleaseForm.Form.Cluster.ID,
-		ProjectID:    form.ReleaseForm.Form.Cluster.ProjectID,
-		Namespace:    form.ReleaseForm.Form.Namespace,
-		Name:         form.ChartTemplateForm.Name,
-		WebhookToken: token,
-		ImageRepoURI: repoStr,
-	}
-
-	_, err = app.Repo.Release().CreateRelease(release)
-
-	if err != nil {
-		app.sendExternalError(err, http.StatusInternalServerError, HTTPError{
-			Code:   ErrReleaseDeploy,
-			Errors: []string{"error creating a webhook: " + err.Error()},
-		}, w)
-	}
-
-	// if github action config is linked, call the github action config handler
-	if form.GithubActionConfig != nil {
-		gaForm := &forms.CreateGitAction{
-			Release: release,
-
-			GitRepo:        form.GithubActionConfig.GitRepo,
-			GitBranch:      form.GithubActionConfig.GitBranch,
-			ImageRepoURI:   form.GithubActionConfig.ImageRepoURI,
-			DockerfilePath: form.GithubActionConfig.DockerfilePath,
-			GitRepoID:      form.GithubActionConfig.GitRepoID,
-			RegistryID:     form.GithubActionConfig.RegistryID,
-
-			ShouldGenerateOnly:   false,
-			ShouldCreateWorkflow: form.GithubActionConfig.ShouldCreateWorkflow,
-		}
-
-		// validate the form
-		if err := app.validator.Struct(form); err != nil {
-			app.handleErrorFormValidation(err, ErrProjectValidateFields, w)
-			return
-		}
-
-		app.createGitActionFromForm(projID, clusterID, form.ChartTemplateForm.Name, form.ReleaseForm.Form.Namespace, gaForm, w, r)
-	}
-
-	app.AnalyticsClient.Track(analytics.ApplicationLaunchSuccessTrack(
-		&analytics.ApplicationLaunchSuccessTrackOpts{
-			ApplicationScopedTrackOpts: analytics.GetApplicationScopedTrackOpts(
-				userID,
-				uint(projID),
-				uint(clusterID),
-				release.Name,
-				release.Namespace,
-				chart.Metadata.Name,
-			),
-			FlowID: flowID,
-		},
-	))
-
-	w.WriteHeader(http.StatusOK)
-}
-
-// HandleDeployAddon triggers a addon deployment from a template
-func (app *App) HandleDeployAddon(w http.ResponseWriter, r *http.Request) {
-	projID, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
-	userID, err := app.getUserIDFromRequest(r)
-	flowID := oauth.CreateRandomState()
-
-	if err != nil || projID == 0 {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	name := chi.URLParam(r, "name")
-	version := chi.URLParam(r, "version")
-
-	// if version passed as latest, pass empty string to loader to get latest
-	if version == "latest" {
-		version = ""
-	}
-
-	getChartForm := &forms.ChartForm{
-		Name:    name,
-		Version: version,
-		RepoURL: app.ServerConf.DefaultApplicationHelmRepoURL,
-	}
-
-	// if a repo_url is passed as query param, it will be populated
-	vals, err := url.ParseQuery(r.URL.RawQuery)
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
-		return
-	}
-
-	getChartForm.PopulateRepoURLFromQueryParams(vals)
-
-	chart, err := loader.LoadChartPublic(getChartForm.RepoURL, getChartForm.Name, getChartForm.Version)
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
-		return
-	}
-
-	form := &forms.InstallChartTemplateForm{
-		ReleaseForm: &forms.ReleaseForm{
-			Form: &helm.Form{
-				Repo:              app.Repo,
-				DigitalOceanOAuth: app.DOConf,
-			},
-		},
-		ChartTemplateForm: &forms.ChartTemplateForm{},
-	}
-
-	form.ReleaseForm.PopulateHelmOptionsFromQueryParams(
-		vals,
-		app.Repo.Cluster(),
-	)
-
-	if err := json.NewDecoder(r.Body).Decode(form); err != nil {
-		app.handleErrorFormDecoding(err, ErrUserDecode, w)
-		return
-	}
-
-	app.AnalyticsClient.Track(analytics.ApplicationLaunchStartTrack(
-		&analytics.ApplicationLaunchStartTrackOpts{
-			ClusterScopedTrackOpts: analytics.GetClusterScopedTrackOpts(userID, uint(projID), uint(form.ReleaseForm.Cluster.ID)),
-			FlowID:                 flowID,
-		},
-	))
-
-	agent, err := app.getAgentFromReleaseForm(
-		w,
-		r,
-		form.ReleaseForm,
-	)
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrUserDecode, w)
-		return
-	}
-
-	registries, err := app.Repo.Registry().ListRegistriesByProjectID(uint(projID))
-
-	if err != nil {
-		app.handleErrorDataRead(err, w)
-		return
-	}
-
-	conf := &helm.InstallChartConfig{
-		Chart:      chart,
-		Name:       form.ChartTemplateForm.Name,
-		Namespace:  form.ReleaseForm.Form.Namespace,
-		Values:     form.ChartTemplateForm.FormValues,
-		Cluster:    form.ReleaseForm.Cluster,
-		Repo:       app.Repo,
-		Registries: registries,
-	}
-
-	rel, err := agent.InstallChart(conf, app.DOConf)
-
-	if err != nil {
-		app.sendExternalError(err, http.StatusInternalServerError, HTTPError{
-			Code:   ErrReleaseDeploy,
-			Errors: []string{"error installing a new chart: " + err.Error()},
-		}, w)
-
-		return
-	}
-
-	app.AnalyticsClient.Track(analytics.ApplicationLaunchSuccessTrack(
-		&analytics.ApplicationLaunchSuccessTrackOpts{
-			ApplicationScopedTrackOpts: analytics.GetApplicationScopedTrackOpts(
-				userID,
-				uint(projID),
-				uint(form.ReleaseForm.Cluster.ID),
-				rel.Name,
-				rel.Namespace,
-				chart.Metadata.Name,
-			),
-			FlowID: flowID,
-		},
-	))
-
-	w.WriteHeader(http.StatusOK)
-}
-
-// HandleUninstallTemplate triggers a chart deployment from a template
-func (app *App) HandleUninstallTemplate(w http.ResponseWriter, r *http.Request) {
-	name := chi.URLParam(r, "name")
-
-	vals, err := url.ParseQuery(r.URL.RawQuery)
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
-		return
-	}
-
-	form := &forms.GetReleaseForm{
-		ReleaseForm: &forms.ReleaseForm{
-			Form: &helm.Form{
-				Repo:              app.Repo,
-				DigitalOceanOAuth: app.DOConf,
-			},
-		},
-		Name: name,
-	}
-
-	agent, err := app.getAgentFromQueryParams(
-		w,
-		r,
-		form.ReleaseForm,
-		form.ReleaseForm.PopulateHelmOptionsFromQueryParams,
-	)
-
-	// errors are handled in app.getAgentFromQueryParams
-	if err != nil {
-		return
-	}
-
-	resp, err := agent.UninstallChart(name)
-	if err != nil {
-		return
-	}
-
-	// update the github actions env if the release exists and is built from source
-	if cName := resp.Release.Chart.Metadata.Name; cName == "job" || cName == "web" || cName == "worker" {
-		clusterID, err := strconv.ParseUint(vals["cluster_id"][0], 10, 64)
-
-		if err != nil {
-			app.sendExternalError(err, http.StatusInternalServerError, HTTPError{
-				Code:   ErrReleaseReadData,
-				Errors: []string{"release not found"},
-			}, w)
-		}
-
-		release, err := app.Repo.Release().ReadRelease(uint(clusterID), name, resp.Release.Namespace)
-
-		if release != nil {
-			gitAction := release.GitActionConfig
-
-			if gitAction.ID != 0 {
-				// parse env into build env
-				cEnv := &ContainerEnvConfig{}
-				rawValues, err := yaml.Marshal(resp.Release.Config)
-
-				if err != nil {
-					app.sendExternalError(err, http.StatusInternalServerError, HTTPError{
-						Code:   ErrReleaseReadData,
-						Errors: []string{"could not get values of previous revision"},
-					}, w)
-				}
-
-				yaml.Unmarshal(rawValues, cEnv)
-
-				gr, err := app.Repo.GitRepo().ReadGitRepo(gitAction.GitRepoID)
-
-				if err != nil {
-					if err != gorm.ErrRecordNotFound {
-						app.handleErrorInternal(err, w)
-						return
-					}
-					gr = nil
-				}
-
-				repoSplit := strings.Split(gitAction.GitRepo, "/")
-
-				projID, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
-
-				if err != nil || projID == 0 {
-					app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-					return
-				}
-
-				gaRunner := &actions.GithubActions{
-					ServerURL:              app.ServerConf.ServerURL,
-					GithubOAuthIntegration: gr,
-					GithubAppID:            app.GithubAppConf.AppID,
-					GithubAppSecretPath:    app.GithubAppConf.SecretPath,
-					GithubInstallationID:   gitAction.GithubInstallationID,
-					GitRepoName:            repoSplit[1],
-					GitRepoOwner:           repoSplit[0],
-					Repo:                   app.Repo,
-					GithubConf:             app.GithubProjectConf,
-					ProjectID:              uint(projID),
-					ReleaseName:            name,
-					ReleaseNamespace:       release.Namespace,
-					GitBranch:              gitAction.GitBranch,
-					DockerFilePath:         gitAction.DockerfilePath,
-					FolderPath:             gitAction.FolderPath,
-					ImageRepoURL:           gitAction.ImageRepoURI,
-					BuildEnv:               cEnv.Container.Env.Normal,
-					ClusterID:              release.ClusterID,
-					Version:                gitAction.Version,
-				}
-
-				err = gaRunner.Cleanup()
-
-				if err != nil {
-					app.sendExternalError(err, http.StatusInternalServerError, HTTPError{
-						Code:   ErrReleaseReadData,
-						Errors: []string{"could not remove github action"},
-					}, w)
-				}
-			}
-		}
-	}
-
-	w.WriteHeader(http.StatusOK)
-	return
-}

+ 0 - 113
server/api/deploy_handler_test.go

@@ -1,113 +0,0 @@
-package api_test
-
-// import (
-// 	"encoding/json"
-// 	"net/http"
-// 	"strings"
-// 	"testing"
-
-// 	"github.com/porter-dev/porter/internal/kubernetes"
-// )
-
-// // ------------------------- TEST TYPES AND MAIN LOOP ------------------------- //
-
-// type deployTest struct {
-// 	initializers []func(tester *tester)
-// 	msg          string
-// 	method       string
-// 	endpoint     string
-// 	body         string
-// 	expStatus    int
-// 	expBody      string
-// 	useCookie    bool
-// 	validators   []func(c *deployTest, tester *tester, t *testing.T)
-// }
-
-// func testDeployRequests(t *testing.T, tests []*deployTest, canQuery bool) {
-// 	for _, c := range tests {
-// 		// create a new tester
-// 		tester := newTester(canQuery)
-
-// 		// if there's an initializer, call it
-// 		for _, init := range c.initializers {
-// 			init(tester)
-// 		}
-
-// 		req, err := http.NewRequest(
-// 			c.method,
-// 			c.endpoint,
-// 			strings.NewReader(c.body),
-// 		)
-
-// 		tester.req = req
-
-// 		if c.useCookie {
-// 			req.AddCookie(tester.cookie)
-// 		}
-
-// 		if err != nil {
-// 			t.Fatal(err)
-// 		}
-
-// 		tester.execute()
-// 		rr := tester.rr
-
-// 		// first, check that the status matches
-// 		if status := rr.Code; status != c.expStatus {
-// 			t.Errorf("%s, handler returned wrong status code: got %v want %v",
-// 				c.msg, status, c.expStatus)
-// 		}
-
-// 		// if there's a validator, call it
-// 		for _, validate := range c.validators {
-// 			validate(c, tester, t)
-// 		}
-// 	}
-// }
-
-// // ------------------------- TEST FIXTURES AND FUNCTIONS  ------------------------- //
-
-// // var newDeployTests = []*deployTest{
-// // 	&deployTest{
-// // 		initializers: []func(tester *tester){
-// // 			initDefaultDeploy,
-// // 		},
-// // 		msg:       "Deploy template",
-// // 		method:    "POST",
-// // 		endpoint:  "/api/projects/1/deploy",
-// // 		body:      "",
-// // 		expStatus: http.StatusOK,
-// // 		expBody:   "unimplemented",
-// // 		useCookie: true,
-// // 		validators: []func(c *deployTest, tester *tester, t *testing.T){
-// // 			deployValidator,
-// // 		},
-// // 	},
-// // }
-
-// // func TestHandleDeployTemplate(t *testing.T) {
-// // 	testDeployRequests(t, newDeployTests, true)
-// // }
-
-// // ------------------------- INITIALIZERS AND VALIDATORS ------------------------- //
-
-// func initDefaultDeploy(tester *tester) {
-// 	initUserDefault(tester)
-
-// 	agent := kubernetes.GetAgentTesting(defaultObjects...)
-
-// 	// overwrite the test agent with new resources
-// 	tester.app.TestAgents.K8sAgent = agent
-// }
-
-// func deployValidator(c *deployTest, tester *tester, t *testing.T) {
-// 	var gotBody map[string]interface{}
-// 	var expBody map[string]interface{}
-
-// 	json.Unmarshal(tester.rr.Body.Bytes(), &gotBody)
-// 	json.Unmarshal([]byte(c.expBody), &expBody)
-
-// 	if string(tester.rr.Body.Bytes()) != c.expBody {
-// 		t.Errorf("Mismatch")
-// 	}
-// }

+ 0 - 91
server/api/dns_record_handler.go

@@ -1,91 +0,0 @@
-package api
-
-import (
-	"encoding/json"
-	"fmt"
-	"net/http"
-	"net/url"
-
-	"github.com/porter-dev/porter/internal/forms"
-	"github.com/porter-dev/porter/internal/kubernetes"
-	"github.com/porter-dev/porter/internal/kubernetes/domain"
-)
-
-// HandleCreateDNSRecord creates a new DNS record
-func (app *App) HandleCreateDNSRecord(w http.ResponseWriter, r *http.Request) {
-	vals, err := url.ParseQuery(r.URL.RawQuery)
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
-		return
-	}
-
-	// get the filter options
-	form := &forms.CreateDomainForm{
-		K8sForm: &forms.K8sForm{
-			OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
-				Repo:              app.Repo,
-				DigitalOceanOAuth: app.DOConf,
-			},
-		},
-	}
-
-	form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster())
-
-	if err := json.NewDecoder(r.Body).Decode(form); err != nil {
-		app.handleErrorFormDecoding(err, ErrUserDecode, w)
-		return
-	}
-
-	// validate the form
-	if err := app.validator.Struct(form); err != nil {
-		app.handleErrorFormValidation(err, ErrK8sValidate, w)
-		return
-	}
-
-	// create a new agent
-	var agent *kubernetes.Agent
-
-	if app.ServerConf.IsTesting {
-		agent = app.TestAgents.K8sAgent
-	} else {
-		agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
-	}
-
-	endpoint, found, err := domain.GetNGINXIngressServiceIP(agent.Clientset)
-
-	if !found {
-		app.handleErrorInternal(fmt.Errorf("target cluster does not have nginx ingress"), w)
-		return
-	}
-
-	createDomain := domain.CreateDNSRecordConfig{
-		ReleaseName: form.ReleaseName,
-		RootDomain:  app.ServerConf.AppRootDomain,
-		Endpoint:    endpoint,
-	}
-
-	record := createDomain.NewDNSRecordForEndpoint()
-
-	record, err = app.Repo.DNSRecord().CreateDNSRecord(record)
-
-	if err != nil {
-		app.handleErrorDataWrite(err, w)
-		return
-	}
-
-	_record := domain.DNSRecord(*record)
-
-	err = _record.CreateDomain(app.IngressAgent.Clientset)
-
-	if err != nil {
-		app.handleErrorInternal(err, w)
-	}
-
-	w.WriteHeader(http.StatusCreated)
-
-	if err := json.NewEncoder(w).Encode(record.Externalize()); err != nil {
-		app.handleErrorFormDecoding(err, ErrK8sDecode, w)
-		return
-	}
-}

+ 0 - 168
server/api/errors.go

@@ -1,168 +0,0 @@
-package api
-
-import (
-	"encoding/json"
-	"net/http"
-
-	"github.com/go-playground/validator/v10"
-	"gorm.io/gorm"
-)
-
-// HTTPError is the object returned when the API encounters an error: this
-// gets marshaled into JSON
-type HTTPError struct {
-	Code   ErrorCode `json:"code"`
-	Errors []string  `json:"errors"`
-}
-
-// ErrorCode is a custom Porter error code, useful for frontend messages
-type ErrorCode int64
-
-var (
-	// ErrorUpgradeWebsocket describes an error while upgrading http to a websocket endpoint.
-	ErrorUpgradeWebsocket = HTTPError{
-		Code: 500,
-		Errors: []string{
-			"could not upgrade to websocket",
-		},
-	}
-
-	// ErrorDataWrite describes an error in writing to the database
-	ErrorDataWrite = HTTPError{
-		Code: 500,
-		Errors: []string{
-			"could not write to database",
-		},
-	}
-
-	// ErrorWebsocketWrite describes an error in writing to websocket connection
-	ErrorWebsocketWrite = HTTPError{
-		Code: 500,
-		Errors: []string{
-			"could not write data via websocket",
-		},
-	}
-
-	// ErrorDataRead describes an error when reading from the database
-	ErrorDataRead = HTTPError{
-		Code: 500,
-		Errors: []string{
-			"could not read from database",
-		},
-	}
-
-	// ErrorInternal describes a generic internal server error
-	ErrorInternal = HTTPError{
-		Code: 500,
-		Errors: []string{
-			"internal server error",
-		},
-	}
-)
-
-// ------------------------ Error helper functions ------------------------ //
-
-// sendExternalError marshals an HTTPError into JSON: this function will return an error if
-// a marshaling error occurs, but only after the internal error header has been sent to the
-// client.
-//
-// It then logs it via the app.logger and sends a formatted error to the client.
-func (app *App) sendExternalError(
-	err error,
-	code int,
-	errExt HTTPError,
-	w http.ResponseWriter,
-) (intErr error) {
-	respBytes, _ := json.Marshal(errExt)
-	respBody := string(respBytes)
-
-	app.Logger.Warn().Err(err).
-		Str("errExt", respBody).
-		Msg("")
-
-	w.WriteHeader(code)
-	w.Write(respBytes)
-
-	return nil
-}
-
-// handleErrorFormDecoding handles an error in decoding process from JSON to the
-// construction of a Form model, and the conversion between a form model and a
-// gorm.Model.
-func (app *App) handleErrorFormDecoding(err error, code ErrorCode, w http.ResponseWriter) {
-	errExt := HTTPError{
-		Code:   code,
-		Errors: []string{"could not process request"},
-	}
-
-	app.sendExternalError(err, http.StatusBadRequest, errExt, w)
-}
-
-// handleErrorFormValidation handles an error in the validation of form fields, and
-// sends a descriptive method about the incorrect fields to the client.
-func (app *App) handleErrorFormValidation(err error, code ErrorCode, w http.ResponseWriter) {
-	// translate all validator errors
-	errs := err.(validator.ValidationErrors)
-	res := make([]string, 0)
-
-	for _, field := range errs {
-		valErr := field.Tag() + " validation failed"
-
-		res = append(res, valErr)
-	}
-
-	errExt := HTTPError{
-		Code:   code,
-		Errors: res,
-	}
-
-	app.sendExternalError(err, http.StatusUnprocessableEntity, errExt, w)
-}
-
-// handleErrorRead handles an error in reading a record from the DB. If the record is
-// not found, the error message is more descriptive; otherwise, a generic dataRead
-// error is sent.
-func (app *App) handleErrorRead(err error, code ErrorCode, w http.ResponseWriter) {
-	// first check if the error is RecordNotFound -- send a more descriptive
-	// message if that is the case
-	if err == gorm.ErrRecordNotFound {
-		errExt := HTTPError{
-			Code:   code,
-			Errors: []string{"could not find requested object"},
-		}
-
-		app.sendExternalError(err, http.StatusNotFound, errExt, w)
-
-		return
-	}
-
-	app.handleErrorDataRead(err, w)
-}
-
-// handleErrorDataWrite handles a database write error due to either a connection
-// error with the database or failure to write that wasn't caught by the validators
-func (app *App) handleErrorDataWrite(err error, w http.ResponseWriter) {
-	app.sendExternalError(err, http.StatusInternalServerError, ErrorDataWrite, w)
-}
-
-// handleErrorWebsocketWrite handles an error from websocket.WriteMessage
-func (app *App) handleErrorWebsocketWrite(err error, w http.ResponseWriter) {
-	app.sendExternalError(err, http.StatusInternalServerError, ErrorWebsocketWrite, w)
-}
-
-// handleErrorUpgradeWebsocket handles error in upgrading a http endpoint to websocket conn
-func (app *App) handleErrorUpgradeWebsocket(err error, w http.ResponseWriter) {
-	app.sendExternalError(err, http.StatusInternalServerError, ErrorUpgradeWebsocket, w)
-}
-
-// handleErrorDataRead handles a database read error due to an internal error, such as
-// the database connection or gorm internals
-func (app *App) handleErrorDataRead(err error, w http.ResponseWriter) {
-	app.sendExternalError(err, http.StatusInternalServerError, ErrorDataRead, w)
-}
-
-// handleErrorInternalError is a catch-all for internal errors that occur during the
-// processing of a request
-func (app *App) handleErrorInternal(err error, w http.ResponseWriter) {
-	app.sendExternalError(err, http.StatusInternalServerError, ErrorInternal, w)
-}

+ 0 - 265
server/api/git_action_handler.go

@@ -1,265 +0,0 @@
-package api
-
-import (
-	"encoding/json"
-	"fmt"
-	"net/http"
-	"net/url"
-	"strconv"
-	"strings"
-
-	"github.com/go-chi/chi"
-	"github.com/porter-dev/porter/internal/auth/token"
-	"github.com/porter-dev/porter/internal/forms"
-	"github.com/porter-dev/porter/internal/integrations/ci/actions"
-	"github.com/porter-dev/porter/internal/models"
-	"github.com/porter-dev/porter/internal/registry"
-)
-
-const (
-	updateAppActionVersion = "v0.1.0"
-)
-
-// HandleGenerateGitAction returns the Github action that will be created in a repository
-// for a given release
-func (app *App) HandleGenerateGitAction(w http.ResponseWriter, r *http.Request) {
-	projID, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 10, 64)
-
-	if err != nil || projID == 0 {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	vals, err := url.ParseQuery(r.URL.RawQuery)
-	name := vals["name"][0]
-	namespace := vals["namespace"][0]
-
-	clusterID, err := strconv.ParseUint(vals["cluster_id"][0], 10, 64)
-
-	if err != nil {
-		app.sendExternalError(err, http.StatusInternalServerError, HTTPError{
-			Code:   ErrReleaseReadData,
-			Errors: []string{"release not found"},
-		}, w)
-		return
-	}
-
-	form := &forms.CreateGitAction{
-		ShouldGenerateOnly: true,
-	}
-
-	// decode from JSON to form value
-	if err := json.NewDecoder(r.Body).Decode(form); err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	_, workflowYAML := app.createGitActionFromForm(projID, clusterID, name, namespace, form, w, r)
-
-	w.WriteHeader(http.StatusOK)
-
-	if _, err := w.Write(workflowYAML); err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-}
-
-// HandleCreateGitAction creates a new Github action in a repository for a given
-// release
-func (app *App) HandleCreateGitAction(w http.ResponseWriter, r *http.Request) {
-	projID, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
-
-	if err != nil || projID == 0 {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	vals, err := url.ParseQuery(r.URL.RawQuery)
-	name := vals["name"][0]
-	namespace := vals["namespace"][0]
-
-	clusterID, err := strconv.ParseUint(vals["cluster_id"][0], 10, 64)
-
-	if err != nil {
-		app.sendExternalError(err, http.StatusInternalServerError, HTTPError{
-			Code:   ErrReleaseReadData,
-			Errors: []string{"release not found"},
-		}, w)
-	}
-
-	release, err := app.Repo.Release().ReadRelease(uint(clusterID), name, namespace)
-
-	if err != nil {
-		app.sendExternalError(err, http.StatusInternalServerError, HTTPError{
-			Code:   ErrReleaseReadData,
-			Errors: []string{"release not found"},
-		}, w)
-	}
-
-	form := &forms.CreateGitAction{
-		Release:            release,
-		ShouldGenerateOnly: false,
-	}
-
-	// decode from JSON to form value
-	if err := json.NewDecoder(r.Body).Decode(form); err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	gaExt, _ := app.createGitActionFromForm(projID, clusterID, name, namespace, form, w, r)
-
-	w.WriteHeader(http.StatusCreated)
-
-	if err := json.NewEncoder(w).Encode(gaExt); err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-}
-
-func (app *App) createGitActionFromForm(
-	projID,
-	clusterID uint64,
-	name, namespace string,
-	form *forms.CreateGitAction,
-	w http.ResponseWriter,
-	r *http.Request,
-) (gaExt *models.GitActionConfigExternal, workflowYAML []byte) {
-	// validate the form
-	if err := app.validator.Struct(form); err != nil {
-		app.handleErrorFormValidation(err, ErrProjectValidateFields, w)
-		return
-	}
-
-	// if the registry was provisioned through Porter, create a repository if necessary
-	if form.RegistryID != 0 {
-		// read the registry
-		reg, err := app.Repo.Registry().ReadRegistry(form.RegistryID)
-
-		if err != nil {
-			app.handleErrorDataRead(err, w)
-			return
-		}
-
-		_reg := registry.Registry(*reg)
-		regAPI := &_reg
-
-		// parse the name from the registry
-		nameSpl := strings.Split(form.ImageRepoURI, "/")
-		repoName := nameSpl[len(nameSpl)-1]
-
-		err = regAPI.CreateRepository(app.Repo, repoName)
-
-		if err != nil {
-			app.handleErrorInternal(err, w)
-			return
-		}
-	}
-
-	repoSplit := strings.Split(form.GitRepo, "/")
-
-	if len(repoSplit) != 2 {
-		app.handleErrorFormDecoding(fmt.Errorf("invalid formatting of repo name"), ErrProjectDecode, w)
-		return
-	}
-
-	session, err := app.Store.Get(r, app.ServerConf.CookieName)
-
-	if err != nil {
-		http.Error(w, err.Error(), http.StatusInternalServerError)
-		return
-	}
-
-	userID, _ := session.Values["user_id"].(uint)
-
-	if userID == 0 {
-		tok := app.getTokenFromRequest(r)
-
-		if tok != nil && tok.IBy != 0 {
-			userID = tok.IBy
-		} else if tok == nil || tok.IBy == 0 {
-			http.Error(w, "no user id found in request", http.StatusInternalServerError)
-			return
-		}
-	}
-
-	// generate porter jwt token
-	jwt, _ := token.GetTokenForAPI(userID, uint(projID))
-
-	encoded, err := jwt.EncodeToken(&token.TokenGeneratorConf{
-		TokenSecret: app.ServerConf.TokenGeneratorSecret,
-	})
-
-	if err != nil {
-		app.handleErrorInternal(err, w)
-		return
-	}
-
-	// create the commit in the git repo
-	gaRunner := &actions.GithubActions{
-		ServerURL:              app.ServerConf.ServerURL,
-		GithubOAuthIntegration: nil,
-		GithubAppID:            app.GithubAppConf.AppID,
-		GithubAppSecretPath:    app.GithubAppConf.SecretPath,
-		GithubInstallationID:   form.GitRepoID,
-		GitRepoName:            repoSplit[1],
-		GitRepoOwner:           repoSplit[0],
-		Repo:                   app.Repo,
-		GithubConf:             app.GithubProjectConf,
-		ProjectID:              uint(projID),
-		ClusterID:              uint(clusterID),
-		ReleaseName:            name,
-		ReleaseNamespace:       namespace,
-		GitBranch:              form.GitBranch,
-		DockerFilePath:         form.DockerfilePath,
-		FolderPath:             form.FolderPath,
-		ImageRepoURL:           form.ImageRepoURI,
-		PorterToken:            encoded,
-		Version:                updateAppActionVersion,
-		ShouldGenerateOnly:     form.ShouldGenerateOnly,
-		ShouldCreateWorkflow:   form.ShouldCreateWorkflow,
-	}
-
-	workflowYAML, err = gaRunner.Setup()
-
-	if err != nil {
-		app.handleErrorInternal(err, w)
-		return
-	}
-
-	if form.Release == nil {
-		return
-	}
-
-	// convert the form to a git action config
-	gitAction, err := form.ToGitActionConfig(gaRunner.Version)
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	// handle write to the database
-	ga, err := app.Repo.GitActionConfig().CreateGitActionConfig(gitAction)
-
-	if err != nil {
-		app.handleErrorDataWrite(err, w)
-		return
-	}
-
-	app.Logger.Info().Msgf("New git action created: %d", ga.ID)
-
-	// update the release in the db with the image repo uri
-	form.Release.ImageRepoURI = gitAction.ImageRepoURI
-
-	_, err = app.Repo.Release().UpdateRelease(form.Release)
-
-	if err != nil {
-		app.handleErrorDataWrite(err, w)
-		return
-	}
-
-	gaExt = ga.Externalize()
-
-	return
-}

+ 0 - 531
server/api/git_repo_handler.go

@@ -1,531 +0,0 @@
-package api
-
-import (
-	"context"
-	"encoding/json"
-	"fmt"
-	"net/http"
-	"net/url"
-	"regexp"
-	"strconv"
-	"strings"
-	"sync"
-
-	"github.com/porter-dev/porter/internal/models"
-	"golang.org/x/oauth2"
-
-	"github.com/bradleyfalzon/ghinstallation"
-	"github.com/go-chi/chi"
-	"github.com/google/go-github/github"
-)
-
-// HandleListProjectGitRepos returns a list of git repos for a project
-func (app *App) HandleListProjectGitRepos(w http.ResponseWriter, r *http.Request) {
-	tok, err := app.getGithubAppOauthTokenFromRequest(r)
-
-	if err != nil {
-		app.Logger.Warn().Err(err).
-			Str("info", "github app oauth token error").
-			Msg("")
-
-		json.NewEncoder(w).Encode(make([]*models.GitRepoExternal, 0))
-		return
-	}
-
-	client := github.NewClient(app.GithubProjectConf.Client(oauth2.NoContext, tok))
-
-	accountIds := make([]int64, 0)
-
-	AuthUser, _, err := client.Users.Get(context.Background(), "")
-
-	if err != nil {
-
-		app.handleErrorInternal(err, w)
-		return
-	}
-
-	accountIds = append(accountIds, *AuthUser.ID)
-
-	opts := &github.ListOptions{
-		PerPage: 100,
-		Page:    1,
-	}
-
-	for {
-		orgs, pages, err := client.Organizations.List(context.Background(), "", opts)
-
-		if err != nil {
-			res := HandleListGithubAppAccessResp{
-				HasAccess: false,
-			}
-			json.NewEncoder(w).Encode(res)
-			return
-		}
-
-		for _, org := range orgs {
-			accountIds = append(accountIds, *org.ID)
-		}
-
-		if pages.NextPage == 0 {
-			break
-		}
-	}
-
-	installationData, err := app.Repo.GithubAppInstallation().ReadGithubAppInstallationByAccountIDs(accountIds)
-
-	if err != nil {
-		app.handleErrorInternal(err, w)
-		return
-	}
-
-	installationIds := make([]int64, 0)
-
-	for _, v := range installationData {
-		installationIds = append(installationIds, v.InstallationID)
-	}
-
-	json.NewEncoder(w).Encode(installationIds)
-}
-
-// Repo represents a GitHub or Gitab repository
-type Repo struct {
-	FullName string
-	Kind     string
-}
-
-// DirectoryItem represents a file or subfolder in a repository
-type DirectoryItem struct {
-	Path string
-	Type string
-}
-
-// AutoBuildpack represents an automatically detected buildpack
-type AutoBuildpack struct {
-	Valid bool   `json:"valid"`
-	Name  string `json:"name"`
-}
-
-// HandleListRepos retrieves a list of repo names
-func (app *App) HandleListRepos(w http.ResponseWriter, r *http.Request) {
-
-	client, err := app.githubAppClientFromRequest(r)
-
-	if err != nil {
-		app.handleErrorInternal(err, w)
-		return
-	}
-
-	// figure out number of repositories
-	opt := &github.ListOptions{
-		PerPage: 100,
-	}
-
-	allRepos, resp, err := client.Apps.ListRepos(context.Background(), opt)
-
-	if err != nil {
-		app.handleErrorInternal(err, w)
-		return
-	}
-
-	// make workers to get pages concurrently
-	const WCOUNT = 5
-	numPages := resp.LastPage + 1
-	var workerErr error
-	var mu sync.Mutex
-	var wg sync.WaitGroup
-
-	worker := func(cp int) {
-		defer wg.Done()
-
-		for cp < numPages {
-			cur_opt := &github.ListOptions{
-				Page:    cp,
-				PerPage: 100,
-			}
-
-			repos, _, err := client.Apps.ListRepos(context.Background(), cur_opt)
-
-			if err != nil {
-				mu.Lock()
-				workerErr = err
-				mu.Unlock()
-				return
-			}
-
-			mu.Lock()
-			allRepos = append(allRepos, repos...)
-			mu.Unlock()
-
-			cp += WCOUNT
-		}
-	}
-
-	var numJobs int
-	if numPages > WCOUNT {
-		numJobs = WCOUNT
-	} else {
-		numJobs = numPages
-	}
-
-	wg.Add(numJobs)
-
-	// page 1 is already loaded so we start with 2
-	for i := 1; i <= numJobs; i++ {
-		go worker(i + 1)
-	}
-
-	wg.Wait()
-
-	if workerErr != nil {
-		app.handleErrorInternal(workerErr, w)
-		return
-	}
-
-	res := make([]Repo, 0)
-
-	for _, repo := range allRepos {
-		res = append(res, Repo{
-			FullName: repo.GetFullName(),
-			Kind:     "github",
-		})
-	}
-
-	json.NewEncoder(w).Encode(res)
-}
-
-// HandleGetBranches retrieves a list of branch names for a specified repo
-func (app *App) HandleGetBranches(w http.ResponseWriter, r *http.Request) {
-
-	client, err := app.githubAppClientFromRequest(r)
-
-	if err != nil {
-		app.handleErrorInternal(err, w)
-		return
-	}
-
-	owner := chi.URLParam(r, "owner")
-	name := chi.URLParam(r, "name")
-
-	// List all branches for a specified repo
-	allBranches, resp, err := client.Repositories.ListBranches(context.Background(), owner, name, &github.ListOptions{
-		PerPage: 100,
-	})
-
-	if err != nil {
-		app.handleErrorInternal(err, w)
-		return
-	}
-
-	// make workers to get branches concurrently
-	const WCOUNT = 5
-	numPages := resp.LastPage + 1
-	var workerErr error
-	var mu sync.Mutex
-	var wg sync.WaitGroup
-
-	worker := func(cp int) {
-		defer wg.Done()
-
-		for cp < numPages {
-			opts := &github.ListOptions{
-				Page:    cp,
-				PerPage: 100,
-			}
-
-			branches, _, err := client.Repositories.ListBranches(context.Background(), owner, name, opts)
-
-			if err != nil {
-				mu.Lock()
-				workerErr = err
-				mu.Unlock()
-				return
-			}
-
-			mu.Lock()
-			allBranches = append(allBranches, branches...)
-			mu.Unlock()
-
-			cp += WCOUNT
-		}
-	}
-
-	var numJobs int
-	if numPages > WCOUNT {
-		numJobs = WCOUNT
-	} else {
-		numJobs = numPages
-	}
-
-	wg.Add(numJobs)
-
-	// page 1 is already loaded so we start with 2
-	for i := 1; i <= numJobs; i++ {
-		go worker(i + 1)
-	}
-
-	wg.Wait()
-
-	if workerErr != nil {
-		app.handleErrorInternal(workerErr, w)
-		return
-	}
-
-	res := make([]string, 0)
-	for _, b := range allBranches {
-		res = append(res, b.GetName())
-	}
-
-	json.NewEncoder(w).Encode(res)
-}
-
-// HandleDetectBuildpack attempts to figure which buildpack will be auto used based on directory contents
-func (app *App) HandleDetectBuildpack(w http.ResponseWriter, r *http.Request) {
-
-	client, err := app.githubAppClientFromRequest(r)
-
-	if err != nil {
-		app.handleErrorInternal(err, w)
-		return
-	}
-
-	queryParams, err := url.ParseQuery(r.URL.RawQuery)
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
-		return
-	}
-
-	owner := chi.URLParam(r, "owner")
-	name := chi.URLParam(r, "name")
-	branch := chi.URLParam(r, "branch")
-
-	repoContentOptions := github.RepositoryContentGetOptions{}
-	repoContentOptions.Ref = branch
-	_, directoryContents, _, err := client.Repositories.GetContents(context.Background(), owner, name, queryParams["dir"][0], &repoContentOptions)
-	if err != nil {
-		app.handleErrorInternal(err, w)
-		return
-	}
-
-	var BREQS = map[string]string{
-		"requirements.txt": "Python",
-		"Gemfile":          "Ruby",
-		"package.json":     "Node.js",
-		"pom.xml":          "Java",
-		"composer.json":    "PHP",
-	}
-
-	res := AutoBuildpack{
-		Valid: true,
-	}
-	matches := 0
-
-	for i := range directoryContents {
-		name := *directoryContents[i].Name
-
-		bname, ok := BREQS[name]
-		if ok {
-			matches++
-			res.Name = bname
-		}
-	}
-
-	if matches != 1 {
-		res.Valid = false
-		res.Name = ""
-	}
-
-	json.NewEncoder(w).Encode(res)
-}
-
-// HandleGetBranchContents retrieves the contents of a specific branch and subdirectory
-func (app *App) HandleGetBranchContents(w http.ResponseWriter, r *http.Request) {
-
-	client, err := app.githubAppClientFromRequest(r)
-
-	if err != nil {
-		app.handleErrorInternal(err, w)
-		return
-	}
-
-	queryParams, err := url.ParseQuery(r.URL.RawQuery)
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
-		return
-	}
-
-	owner := chi.URLParam(r, "owner")
-	name := chi.URLParam(r, "name")
-	branch, err := url.QueryUnescape(chi.URLParam(r, "branch"))
-
-	if err != nil {
-		app.handleErrorInternal(err, w)
-		return
-	}
-
-	repoContentOptions := github.RepositoryContentGetOptions{}
-	repoContentOptions.Ref = branch
-	_, directoryContents, _, err := client.Repositories.GetContents(context.Background(), owner, name, queryParams["dir"][0], &repoContentOptions)
-	if err != nil {
-		app.handleErrorInternal(err, w)
-		return
-	}
-
-	res := []DirectoryItem{}
-	for i := range directoryContents {
-		d := DirectoryItem{}
-		d.Path = *directoryContents[i].Path
-		d.Type = *directoryContents[i].Type
-		res = append(res, d)
-	}
-
-	// Ret2: recursively traverse all dirs to create config bundle (case on type == dir)
-	// https://api.github.com/repos/porter-dev/porter/contents?ref=frontend-graph
-	json.NewEncoder(w).Encode(res)
-}
-
-type GetProcfileContentsResp map[string]string
-
-var procfileRegex = regexp.MustCompile("^([A-Za-z0-9_]+):\\s*(.+)$")
-
-// HandleGetProcfileContents retrieves the contents of a procfile in a github repo
-func (app *App) HandleGetProcfileContents(w http.ResponseWriter, r *http.Request) {
-
-	client, err := app.githubAppClientFromRequest(r)
-
-	if err != nil {
-		app.handleErrorInternal(err, w)
-		return
-	}
-
-	owner := chi.URLParam(r, "owner")
-	name := chi.URLParam(r, "name")
-	branch, err := url.QueryUnescape(chi.URLParam(r, "branch"))
-
-	if err != nil {
-		app.handleErrorInternal(err, w)
-		return
-	}
-
-	queryParams, err := url.ParseQuery(r.URL.RawQuery)
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
-		return
-	}
-
-	resp, _, _, err := client.Repositories.GetContents(
-		context.TODO(),
-		owner,
-		name,
-		queryParams["path"][0],
-		&github.RepositoryContentGetOptions{
-			Ref: branch,
-		},
-	)
-
-	if err != nil {
-		http.NotFound(w, r)
-		return
-	}
-
-	fileData, err := resp.GetContent()
-
-	if err != nil {
-		app.handleErrorInternal(err, w)
-		return
-	}
-
-	parsedContents := make(GetProcfileContentsResp)
-
-	// parse the procfile information
-	for _, line := range strings.Split(fileData, "\n") {
-		if matches := procfileRegex.FindStringSubmatch(line); matches != nil {
-			parsedContents[matches[1]] = matches[2]
-		}
-	}
-
-	json.NewEncoder(w).Encode(parsedContents)
-}
-
-type HandleGetRepoZIPDownloadURLResp struct {
-	URLString       string `json:"url"`
-	LatestCommitSHA string `json:"latest_commit_sha"`
-}
-
-// HandleGetRepoZIPDownloadURL gets the URL for downloading a zip file from a Github
-// repository
-func (app *App) HandleGetRepoZIPDownloadURL(w http.ResponseWriter, r *http.Request) {
-
-	client, err := app.githubAppClientFromRequest(r)
-
-	if err != nil {
-		app.handleErrorInternal(err, w)
-		return
-	}
-
-	owner := chi.URLParam(r, "owner")
-	name := chi.URLParam(r, "name")
-	branch, err := url.QueryUnescape(chi.URLParam(r, "branch"))
-
-	if err != nil {
-		app.handleErrorInternal(err, w)
-		return
-	}
-
-	branchResp, _, err := client.Repositories.GetBranch(
-		context.TODO(),
-		owner,
-		name,
-		branch,
-	)
-
-	if err != nil {
-		app.handleErrorInternal(err, w)
-		return
-	}
-
-	ghURL, _, err := client.Repositories.GetArchiveLink(
-		context.TODO(),
-		owner,
-		name,
-		github.Zipball,
-		&github.RepositoryContentGetOptions{
-			Ref: *branchResp.Commit.SHA,
-		},
-	)
-
-	if err != nil {
-		app.handleErrorInternal(err, w)
-		return
-	}
-
-	apiResp := HandleGetRepoZIPDownloadURLResp{
-		URLString:       ghURL.String(),
-		LatestCommitSHA: *branchResp.Commit.SHA,
-	}
-
-	json.NewEncoder(w).Encode(apiResp)
-}
-
-// githubAppClientFromRequest gets the github app installation id from the request and authenticates
-// using it and a private key file
-func (app *App) githubAppClientFromRequest(r *http.Request) (*github.Client, error) {
-
-	installationID, err := strconv.ParseUint(chi.URLParam(r, "installation_id"), 0, 64)
-
-	if err != nil || installationID == 0 {
-		return nil, fmt.Errorf("could not read installation id")
-	}
-
-	itr, err := ghinstallation.NewKeyFromFile(
-		http.DefaultTransport,
-		app.GithubAppConf.AppID,
-		int64(installationID),
-		app.GithubAppConf.SecretPath)
-
-	if err != nil {
-		return nil, err
-	}
-
-	return github.NewClient(&http.Client{Transport: itr}), nil
-}

+ 0 - 113
server/api/git_repo_handler_test.go

@@ -1,113 +0,0 @@
-package api_test
-
-// import (
-// 	"encoding/json"
-// 	"net/http"
-// 	"strings"
-// 	"testing"
-
-// 	"github.com/porter-dev/porter/internal/kubernetes"
-// )
-
-// // ------------------------- TEST TYPES AND MAIN LOOP ------------------------- //
-
-// type reposTest struct {
-// 	initializers []func(tester *tester)
-// 	msg          string
-// 	method       string
-// 	endpoint     string
-// 	body         string
-// 	expStatus    int
-// 	expBody      string
-// 	useCookie    bool
-// 	validators   []func(c *reposTest, tester *tester, t *testing.T)
-// }
-
-// func testReposRequests(t *testing.T, tests []*reposTest, canQuery bool) {
-// 	for _, c := range tests {
-// 		// create a new tester
-// 		tester := newTester(canQuery)
-
-// 		// if there's an initializer, call it
-// 		for _, init := range c.initializers {
-// 			init(tester)
-// 		}
-
-// 		req, err := http.NewRequest(
-// 			c.method,
-// 			c.endpoint,
-// 			strings.NewReader(c.body),
-// 		)
-
-// 		tester.req = req
-
-// 		if c.useCookie {
-// 			req.AddCookie(tester.cookie)
-// 		}
-
-// 		if err != nil {
-// 			t.Fatal(err)
-// 		}
-
-// 		tester.execute()
-// 		rr := tester.rr
-
-// 		// first, check that the status matches
-// 		if status := rr.Code; status != c.expStatus {
-// 			t.Errorf("%s, handler returned wrong status code: got %v want %v",
-// 				c.msg, status, c.expStatus)
-// 		}
-
-// 		// if there's a validator, call it
-// 		for _, validate := range c.validators {
-// 			validate(c, tester, t)
-// 		}
-// 	}
-// }
-
-// // ------------------------- TEST FIXTURES AND FUNCTIONS  ------------------------- //
-
-// // var listReposTests = []*reposTest{
-// // 	&reposTest{
-// // 		initializers: []func(tester *tester){
-// // 			initDefaultRepos,
-// // 		},
-// // 		msg:       "List repos",
-// // 		method:    "GET",
-// // 		endpoint:  "/api/repos/github/porter/master/contents?dir=" + url.QueryEscape("./"),
-// // 		body:      "",
-// // 		expStatus: http.StatusOK,
-// // 		expBody:   "unimplemented",
-// // 		useCookie: true,
-// // 		validators: []func(c *reposTest, tester *tester, t *testing.T){
-// // 			reposListValidator,
-// // 		},
-// // 	},
-// // }
-
-// // func TestHandleListRepos(t *testing.T) {
-// // 	testReposRequests(t, listReposTests, true)
-// // }
-
-// // ------------------------- INITIALIZERS AND VALIDATORS ------------------------- //
-
-// func initDefaultRepos(tester *tester) {
-// 	initUserDefault(tester)
-
-// 	agent := kubernetes.GetAgentTesting(defaultObjects...)
-
-// 	// overwrite the test agent with new resources
-// 	tester.app.TestAgents.K8sAgent = agent
-// }
-
-// func reposListValidator(c *reposTest, tester *tester, t *testing.T) {
-// 	var gotBody map[string]interface{}
-// 	var expBody map[string]interface{}
-
-// 	json.Unmarshal(tester.rr.Body.Bytes(), &gotBody)
-// 	json.Unmarshal([]byte(c.expBody), &expBody)
-
-// 	if string(tester.rr.Body.Bytes()) != c.expBody {
-// 		t.Errorf("Mismatch")
-// 	}
-// }

+ 0 - 44
server/api/health_handler.go

@@ -1,44 +0,0 @@
-package api
-
-import (
-	"net/http"
-)
-
-// HandleLive responds immediately with an HTTP 200 status.
-func (app *App) HandleLive(w http.ResponseWriter, r *http.Request) {
-	writeHealthy(w)
-}
-
-// HandleReady responds with HTTP 200 if healthy, 500 otherwise
-func (app *App) HandleReady(w http.ResponseWriter, r *http.Request) {
-	if app.db == nil {
-		writeHealthy(w)
-		return
-	}
-
-	db, err := app.db.DB()
-
-	if err != nil {
-		app.handleErrorInternal(err, w)
-		return
-	}
-
-	if err := db.Ping(); err != nil {
-		app.handleErrorInternal(err, w)
-		return
-	}
-
-	writeHealthy(w)
-}
-
-func writeHealthy(w http.ResponseWriter) {
-	w.Header().Set("Content-Type", "text/plain")
-	w.WriteHeader(http.StatusOK)
-	w.Write([]byte("."))
-}
-
-func writeUnhealthy(w http.ResponseWriter) {
-	w.Header().Set("Content-Type", "text/plain")
-	w.WriteHeader(http.StatusInternalServerError)
-	w.Write([]byte("."))
-}

+ 0 - 131
server/api/helm_repo_handler.go

@@ -1,131 +0,0 @@
-package api
-
-import (
-	"encoding/json"
-	"net/http"
-	"strconv"
-
-	"github.com/porter-dev/porter/internal/helm/repo"
-
-	"github.com/go-chi/chi"
-	"github.com/porter-dev/porter/internal/forms"
-	"github.com/porter-dev/porter/internal/models"
-)
-
-// HandleCreateHelmRepo creates a new helm repo for a project
-func (app *App) HandleCreateHelmRepo(w http.ResponseWriter, r *http.Request) {
-	projID, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
-
-	if err != nil || projID == 0 {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	form := &forms.CreateHelmRepo{
-		ProjectID: uint(projID),
-	}
-
-	// decode from JSON to form value
-	if err := json.NewDecoder(r.Body).Decode(form); err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	// validate the form
-	if err := app.validator.Struct(form); err != nil {
-		app.handleErrorFormValidation(err, ErrProjectValidateFields, w)
-		return
-	}
-
-	// convert the form to a registry
-	hr, err := form.ToHelmRepo()
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	// handle write to the database
-	hr, err = app.Repo.HelmRepo().CreateHelmRepo(hr)
-
-	if err != nil {
-		app.handleErrorDataWrite(err, w)
-		return
-	}
-
-	app.Logger.Info().Msgf("New helm repo created: %d", hr.ID)
-
-	w.WriteHeader(http.StatusCreated)
-
-	hrExt := hr.Externalize()
-
-	if err := json.NewEncoder(w).Encode(hrExt); err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-}
-
-// HandleListProjectHelmRepos returns a list of helm repos for a project
-func (app *App) HandleListProjectHelmRepos(w http.ResponseWriter, r *http.Request) {
-	projID, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
-
-	if err != nil || projID == 0 {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	hrs, err := app.Repo.HelmRepo().ListHelmReposByProjectID(uint(projID))
-
-	if err != nil {
-		app.handleErrorRead(err, ErrProjectDataRead, w)
-		return
-	}
-
-	extHRs := make([]*models.HelmRepoExternal, 0)
-
-	for _, hr := range hrs {
-		extHRs = append(extHRs, hr.Externalize())
-	}
-
-	w.WriteHeader(http.StatusOK)
-
-	if err := json.NewEncoder(w).Encode(extHRs); err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-}
-
-// HandleListHelmRepoCharts lists the charts for a given linked helm repo
-func (app *App) HandleListHelmRepoCharts(w http.ResponseWriter, r *http.Request) {
-	helmID, err := strconv.ParseUint(chi.URLParam(r, "helm_id"), 0, 64)
-
-	if err != nil || helmID == 0 {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	hr, err := app.Repo.HelmRepo().ReadHelmRepo(uint(helmID))
-
-	if err != nil {
-		app.handleErrorRead(err, ErrProjectDataRead, w)
-		return
-	}
-
-	// cast to a registry from registry package
-	_hr := repo.HelmRepo(*hr)
-	hrAPI := &_hr
-
-	charts, err := hrAPI.ListCharts(app.Repo)
-
-	if err != nil {
-		app.handleErrorRead(err, ErrProjectDataRead, w)
-		return
-	}
-
-	w.WriteHeader(http.StatusOK)
-
-	if err := json.NewEncoder(w).Encode(charts); err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-}

+ 0 - 157
server/api/helm_repo_handler_test.go

@@ -1,157 +0,0 @@
-package api_test
-
-// import (
-// 	"encoding/json"
-// 	"net/http"
-// 	"strings"
-// 	"testing"
-
-// 	"github.com/go-test/deep"
-// 	"github.com/porter-dev/porter/internal/models"
-// )
-
-// // ------------------------- TEST TYPES AND MAIN LOOP ------------------------- //
-
-// type helmTest struct {
-// 	initializers []func(t *tester)
-// 	msg          string
-// 	method       string
-// 	endpoint     string
-// 	body         string
-// 	expStatus    int
-// 	expBody      string
-// 	useCookie    bool
-// 	validators   []func(c *helmTest, tester *tester, t *testing.T)
-// }
-
-// func testHelmRepoRequests(t *testing.T, tests []*helmTest, canQuery bool) {
-// 	for _, c := range tests {
-// 		// create a new tester
-// 		tester := newTester(canQuery)
-
-// 		// if there's an initializer, call it
-// 		for _, init := range c.initializers {
-// 			init(tester)
-// 		}
-
-// 		req, err := http.NewRequest(
-// 			c.method,
-// 			c.endpoint,
-// 			strings.NewReader(c.body),
-// 		)
-
-// 		tester.req = req
-
-// 		if c.useCookie {
-// 			req.AddCookie(tester.cookie)
-// 		}
-
-// 		if err != nil {
-// 			t.Fatal(err)
-// 		}
-
-// 		tester.execute()
-// 		rr := tester.rr
-
-// 		// first, check that the status matches
-// 		if status := rr.Code; status != c.expStatus {
-// 			t.Errorf("%s, handler returned wrong status code: got %v want %v",
-// 				c.msg, status, c.expStatus)
-// 		}
-
-// 		// if there's a validator, call it
-// 		for _, validate := range c.validators {
-// 			validate(c, tester, t)
-// 		}
-// 	}
-// }
-
-// // ------------------------- TEST FIXTURES AND FUNCTIONS  ------------------------- //
-
-// var createHelmRepoTests = []*helmTest{
-// 	{
-// 		initializers: []func(t *tester){
-// 			initUserDefault,
-// 			initProject,
-// 		},
-// 		msg:       "Create helm repo",
-// 		method:    "POST",
-// 		endpoint:  "/api/projects/1/helmrepos",
-// 		body:      `{"name":"helm-repo-test","basic_integration_id":1,"repo_url":"https://example-repo.com"}`,
-// 		expStatus: http.StatusCreated,
-// 		expBody:   `{"id":1,"project_id":1,"name":"helm-repo-test","repo_name":"https://example-repo.com","service":"helm"}`,
-// 		useCookie: true,
-// 		validators: []func(c *helmTest, tester *tester, t *testing.T){
-// 			helmRepoBodyValidator,
-// 		},
-// 	},
-// }
-
-// func TestHandleCreateHelmRepo(t *testing.T) {
-// 	testHelmRepoRequests(t, createHelmRepoTests, true)
-// }
-
-// var listHelmReposTest = []*helmTest{
-// 	{
-// 		initializers: []func(t *tester){
-// 			initUserDefault,
-// 			initProject,
-// 			initHelmRepo,
-// 		},
-// 		msg:       "List helm repos",
-// 		method:    "GET",
-// 		endpoint:  "/api/projects/1/helmrepos",
-// 		body:      ``,
-// 		expStatus: http.StatusOK,
-// 		expBody:   `[{"id":1,"project_id":1,"name":"helm-repo-test","repo_name":"https://example-repo.com","service":"helm"}]`,
-// 		useCookie: true,
-// 		validators: []func(c *helmTest, tester *tester, t *testing.T){
-// 			hrsBodyValidator,
-// 		},
-// 	},
-// }
-
-// func TestHandleListHelmRepos(t *testing.T) {
-// 	testHelmRepoRequests(t, listHelmReposTest, true)
-// }
-
-// // ------------------------- INITIALIZERS AND VALIDATORS ------------------------- //
-
-// func initHelmRepo(tester *tester) {
-// 	proj, _ := tester.repo.Project().ReadProject(1)
-
-// 	hr := &models.HelmRepo{
-// 		Name:                   "helm-repo-test",
-// 		ProjectID:              proj.Model.ID,
-// 		RepoURL:                "https://example-repo.com",
-// 		BasicAuthIntegrationID: 1,
-// 	}
-
-// 	tester.repo.HelmRepo().CreateHelmRepo(hr)
-// }
-
-// func helmRepoBodyValidator(c *helmTest, tester *tester, t *testing.T) {
-// 	gotBody := &models.HelmRepoExternal{}
-// 	expBody := &models.HelmRepoExternal{}
-
-// 	json.Unmarshal(tester.rr.Body.Bytes(), &gotBody)
-// 	json.Unmarshal([]byte(c.expBody), &expBody)
-
-// 	if diff := deep.Equal(gotBody, expBody); diff != nil {
-// 		t.Errorf("handler returned wrong body:\n")
-// 		t.Error(diff)
-// 	}
-// }
-
-// func hrsBodyValidator(c *helmTest, tester *tester, t *testing.T) {
-// 	gotBody := make([]*models.HelmRepoExternal, 0)
-// 	expBody := make([]*models.HelmRepoExternal, 0)
-
-// 	json.Unmarshal(tester.rr.Body.Bytes(), &gotBody)
-// 	json.Unmarshal([]byte(c.expBody), &expBody)
-
-// 	if diff := deep.Equal(gotBody, expBody); diff != nil {
-// 		t.Errorf("handler returned wrong body:\n")
-// 		t.Error(diff)
-// 	}
-// }

+ 0 - 156
server/api/helpers_test.go

@@ -1,156 +0,0 @@
-package api_test
-
-import (
-	"net/http"
-	"net/http/httptest"
-	"strings"
-	"time"
-
-	"github.com/go-chi/chi"
-
-	"github.com/porter-dev/porter/api/server/shared/config/env"
-	"github.com/porter-dev/porter/api/server/shared/config/loader"
-	"github.com/porter-dev/porter/internal/adapter"
-	"github.com/porter-dev/porter/internal/auth/sessionstore"
-	"github.com/porter-dev/porter/internal/helm"
-	"github.com/porter-dev/porter/internal/kubernetes"
-	lr "github.com/porter-dev/porter/internal/logger"
-	"github.com/porter-dev/porter/internal/models"
-	ints "github.com/porter-dev/porter/internal/models/integrations"
-	"github.com/porter-dev/porter/internal/repository"
-	"github.com/porter-dev/porter/internal/repository/gorm"
-	"github.com/porter-dev/porter/server/api"
-	"github.com/porter-dev/porter/server/router"
-)
-
-type tester struct {
-	app    *api.App
-	repo   repository.Repository
-	store  *sessionstore.PGStore
-	router *chi.Mux
-	req    *http.Request
-	rr     *httptest.ResponseRecorder
-	cookie *http.Cookie
-}
-
-func (t *tester) execute() {
-	t.router.ServeHTTP(t.rr, t.req)
-}
-
-func (t *tester) reset() {
-	t.rr = httptest.NewRecorder()
-	t.req = nil
-}
-
-func (t *tester) createUserSession(email string, pw string) {
-	req, err := http.NewRequest(
-		"POST",
-		"/api/users",
-		strings.NewReader(`{"email":"`+email+`","password":"`+pw+`"}`),
-	)
-
-	if err != nil {
-		panic(err)
-	}
-
-	t.req = req
-	t.execute()
-
-	if cookies := t.rr.Result().Cookies(); len(cookies) > 0 {
-		t.cookie = cookies[0]
-	}
-
-	t.reset()
-}
-
-func newTester(canQuery bool) *tester {
-	appConf := loader.EnvConf{
-		ServerConf: &env.ServerConf{
-			Debug:                true,
-			Port:                 8080,
-			CookieName:           "porter",
-			CookieSecrets:        []string{"secret"},
-			TimeoutRead:          time.Second * 5,
-			TimeoutWrite:         time.Second * 10,
-			TimeoutIdle:          time.Second * 15,
-			IsTesting:            true,
-			TokenGeneratorSecret: "secret",
-			BasicLoginEnabled:    true,
-		},
-		// unimportant here
-		DBConf: &env.DBConf{},
-	}
-
-	logger := lr.NewConsole(appConf.ServerConf.Debug)
-
-	db, _ := adapter.New(&env.DBConf{
-		EncryptionKey: "__random_strong_encryption_key__",
-		SQLLite:       true,
-		SQLLitePath:   "api_test.db",
-	})
-
-	db.AutoMigrate(
-		&models.Project{},
-		&models.Role{},
-		&models.User{},
-		&models.Session{},
-		&models.GitRepo{},
-		&models.Registry{},
-		&models.Release{},
-		&models.HelmRepo{},
-		&models.Cluster{},
-		&models.ClusterCandidate{},
-		&models.ClusterResolver{},
-		&models.Infra{},
-		&models.GitActionConfig{},
-		&models.Invite{},
-		&ints.KubeIntegration{},
-		&ints.BasicIntegration{},
-		&ints.OIDCIntegration{},
-		&ints.OAuthIntegration{},
-		&ints.GCPIntegration{},
-		&ints.AWSIntegration{},
-		&ints.ClusterTokenCache{},
-		&ints.RegTokenCache{},
-		&ints.HelmRepoTokenCache{},
-		&ints.GithubAppInstallation{},
-	)
-
-	var key [32]byte
-
-	for i, b := range []byte("__random_strong_encryption_key__") {
-		key[i] = b
-	}
-
-	repo := gorm.NewRepository(db, &key)
-	store, _ := sessionstore.NewStore(
-		&sessionstore.NewStoreOpts{
-			SessionRepository: repo.Session(),
-			CookieSecrets:     []string{"secret"},
-		},
-	)
-	k8sAgent := kubernetes.GetAgentTesting()
-
-	app, _ := api.New(&api.AppConfig{
-		Logger:     logger,
-		Repository: repo,
-		ServerConf: appConf.ServerConf,
-		TestAgents: &api.TestAgents{
-			HelmAgent:             helm.GetAgentTesting(&helm.Form{}, nil, logger, k8sAgent),
-			HelmTestStorageDriver: helm.StorageMap["memory"](nil, nil, ""),
-			K8sAgent:              k8sAgent,
-		},
-	})
-
-	r := router.New(app)
-
-	return &tester{
-		app:    app,
-		repo:   repo,
-		store:  store,
-		router: r,
-		req:    nil,
-		rr:     httptest.NewRecorder(),
-		cookie: nil,
-	}
-}

+ 0 - 41
server/api/infra_handler.go

@@ -1,41 +0,0 @@
-package api
-
-import (
-	"encoding/json"
-	"net/http"
-	"strconv"
-
-	"github.com/go-chi/chi"
-
-	"github.com/porter-dev/porter/api/types"
-)
-
-// HandleListProjectInfra returns a list of infrasa for a project
-func (app *App) HandleListProjectInfra(w http.ResponseWriter, r *http.Request) {
-	projID, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
-
-	if err != nil || projID == 0 {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	infras, err := app.Repo.Infra().ListInfrasByProjectID(uint(projID))
-
-	if err != nil {
-		app.handleErrorRead(err, ErrProjectDataRead, w)
-		return
-	}
-
-	extInfras := make([]*types.Infra, 0)
-
-	for _, infra := range infras {
-		extInfras = append(extInfras, infra.ToInfraType())
-	}
-
-	w.WriteHeader(http.StatusOK)
-
-	if err := json.NewEncoder(w).Encode(extInfras); err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-}

+ 0 - 626
server/api/integration_handler.go

@@ -1,626 +0,0 @@
-package api
-
-import (
-	"context"
-	"crypto/hmac"
-	"crypto/sha256"
-	"encoding/hex"
-	"encoding/json"
-	"fmt"
-	"io/ioutil"
-	"net/http"
-	"net/url"
-	"sort"
-	"strconv"
-	"strings"
-
-	"github.com/go-chi/chi"
-	"github.com/google/go-github/github"
-	"github.com/porter-dev/porter/internal/analytics"
-	"github.com/porter-dev/porter/internal/forms"
-	"github.com/porter-dev/porter/internal/oauth"
-	"golang.org/x/oauth2"
-	"gorm.io/gorm"
-
-	"github.com/porter-dev/porter/internal/models/integrations"
-	ints "github.com/porter-dev/porter/internal/models/integrations"
-)
-
-// HandleListClusterIntegrations lists the cluster integrations available to the
-// instance
-func (app *App) HandleListClusterIntegrations(w http.ResponseWriter, r *http.Request) {
-	clusters := ints.PorterClusterIntegrations
-
-	w.WriteHeader(http.StatusOK)
-
-	if err := json.NewEncoder(w).Encode(&clusters); err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-}
-
-// HandleListRegistryIntegrations lists the image registry integrations available to the
-// instance
-func (app *App) HandleListRegistryIntegrations(w http.ResponseWriter, r *http.Request) {
-	registries := ints.PorterRegistryIntegrations
-
-	w.WriteHeader(http.StatusOK)
-
-	if err := json.NewEncoder(w).Encode(&registries); err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-}
-
-// HandleListHelmRepoIntegrations lists the Helm repo integrations available to the
-// instance
-func (app *App) HandleListHelmRepoIntegrations(w http.ResponseWriter, r *http.Request) {
-	hrs := ints.PorterHelmRepoIntegrations
-
-	w.WriteHeader(http.StatusOK)
-
-	if err := json.NewEncoder(w).Encode(&hrs); err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-}
-
-// HandleListRepoIntegrations lists the repo integrations available to the
-// instance
-func (app *App) HandleListRepoIntegrations(w http.ResponseWriter, r *http.Request) {
-	repos := ints.PorterGitRepoIntegrations
-
-	w.WriteHeader(http.StatusOK)
-
-	if err := json.NewEncoder(w).Encode(&repos); err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-}
-
-// HandleCreateGCPIntegration creates a new GCP integration in the DB
-func (app *App) HandleCreateGCPIntegration(w http.ResponseWriter, r *http.Request) {
-	userID, err := app.getUserIDFromRequest(r)
-
-	if err != nil {
-		http.Error(w, err.Error(), http.StatusInternalServerError)
-		return
-	}
-
-	projID, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
-
-	if err != nil || projID == 0 {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	form := &forms.CreateGCPIntegrationForm{
-		UserID:    userID,
-		ProjectID: uint(projID),
-	}
-
-	// decode from JSON to form value
-	if err := json.NewDecoder(r.Body).Decode(form); err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	// validate the form
-	if err := app.validator.Struct(form); err != nil {
-		app.handleErrorFormValidation(err, ErrProjectValidateFields, w)
-		return
-	}
-
-	// convert the form to a gcp integration
-	gcp, err := form.ToGCPIntegration()
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	// handle write to the database
-	gcp, err = app.Repo.GCPIntegration().CreateGCPIntegration(gcp)
-
-	if err != nil {
-		app.handleErrorDataWrite(err, w)
-		return
-	}
-
-	app.Logger.Info().Msgf("New gcp integration created: %d", gcp.ID)
-
-	w.WriteHeader(http.StatusCreated)
-
-	gcpExt := gcp.Externalize()
-
-	if err := json.NewEncoder(w).Encode(gcpExt); err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-}
-
-// HandleCreateAWSIntegration creates a new AWS integration in the DB
-func (app *App) HandleCreateAWSIntegration(w http.ResponseWriter, r *http.Request) {
-	userID, err := app.getUserIDFromRequest(r)
-
-	if err != nil {
-		http.Error(w, err.Error(), http.StatusInternalServerError)
-		return
-	}
-
-	projID, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
-
-	if err != nil || projID == 0 {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	form := &forms.CreateAWSIntegrationForm{
-		UserID:    userID,
-		ProjectID: uint(projID),
-	}
-
-	// decode from JSON to form value
-	if err := json.NewDecoder(r.Body).Decode(form); err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	// validate the form
-	if err := app.validator.Struct(form); err != nil {
-		app.handleErrorFormValidation(err, ErrProjectValidateFields, w)
-		return
-	}
-
-	// convert the form to a aws integration
-	aws, err := form.ToAWSIntegration()
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	// handle write to the database
-	aws, err = app.Repo.AWSIntegration().CreateAWSIntegration(aws)
-
-	if err != nil {
-		app.handleErrorDataWrite(err, w)
-		return
-	}
-
-	app.Logger.Info().Msgf("New aws integration created: %d", aws.ID)
-
-	w.WriteHeader(http.StatusCreated)
-
-	awsExt := aws.Externalize()
-
-	if err := json.NewEncoder(w).Encode(awsExt); err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-}
-
-// HandleOverwriteAWSIntegration overwrites the ID of an AWS integration in the DB
-func (app *App) HandleOverwriteAWSIntegration(w http.ResponseWriter, r *http.Request) {
-	userID, err := app.getUserIDFromRequest(r)
-
-	if err != nil {
-		http.Error(w, err.Error(), http.StatusInternalServerError)
-		return
-	}
-
-	projID, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
-
-	if err != nil || projID == 0 {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	awsIntegrationID, err := strconv.ParseUint(chi.URLParam(r, "aws_integration_id"), 0, 64)
-
-	if err != nil || awsIntegrationID == 0 {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	form := &forms.OverwriteAWSIntegrationForm{
-		UserID:    userID,
-		ProjectID: uint(projID),
-	}
-
-	// decode from JSON to form value
-	if err := json.NewDecoder(r.Body).Decode(form); err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	// validate the form
-	if err := app.validator.Struct(form); err != nil {
-		app.handleErrorFormValidation(err, ErrProjectValidateFields, w)
-		return
-	}
-
-	// read the aws integration by ID and overwrite the access id/secret
-	awsIntegration, err := app.Repo.AWSIntegration().ReadAWSIntegration(uint(awsIntegrationID))
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	awsIntegration.AWSAccessKeyID = []byte(form.AWSAccessKeyID)
-	awsIntegration.AWSSecretAccessKey = []byte(form.AWSSecretAccessKey)
-
-	// handle write to the database
-	awsIntegration, err = app.Repo.AWSIntegration().OverwriteAWSIntegration(awsIntegration)
-
-	if err != nil {
-		app.handleErrorDataWrite(err, w)
-		return
-	}
-
-	// clear the cluster token cache if cluster_id exists
-	vals, err := url.ParseQuery(r.URL.RawQuery)
-
-	if err != nil {
-		app.handleErrorDataWrite(err, w)
-		return
-	}
-
-	if len(vals["cluster_id"]) > 0 {
-		clusterID, err := strconv.ParseUint(vals["cluster_id"][0], 10, 64)
-
-		if err != nil {
-			app.handleErrorDataWrite(err, w)
-			return
-		}
-
-		cluster, err := app.Repo.Cluster().ReadCluster(uint(clusterID))
-
-		// clear the token
-		cluster.TokenCache.Token = []byte("")
-
-		cluster, err = app.Repo.Cluster().UpdateClusterTokenCache(&cluster.TokenCache)
-
-		if err != nil {
-			app.handleErrorDataWrite(err, w)
-			return
-		}
-	}
-
-	app.Logger.Info().Msgf("AWS integration overwritten: %d", awsIntegration.ID)
-
-	w.WriteHeader(http.StatusCreated)
-
-	awsExt := awsIntegration.Externalize()
-
-	if err := json.NewEncoder(w).Encode(awsExt); err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-}
-
-// HandleCreateBasicAuthIntegration creates a new basic auth integration in the DB
-func (app *App) HandleCreateBasicAuthIntegration(w http.ResponseWriter, r *http.Request) {
-	userID, err := app.getUserIDFromRequest(r)
-
-	if err != nil {
-		http.Error(w, err.Error(), http.StatusInternalServerError)
-		return
-	}
-
-	projID, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
-
-	if err != nil || projID == 0 {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	form := &forms.CreateBasicAuthIntegrationForm{
-		UserID:    userID,
-		ProjectID: uint(projID),
-	}
-
-	// decode from JSON to form value
-	if err := json.NewDecoder(r.Body).Decode(form); err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	// validate the form
-	if err := app.validator.Struct(form); err != nil {
-		app.handleErrorFormValidation(err, ErrProjectValidateFields, w)
-		return
-	}
-
-	// convert the form to a gcp integration
-	basic, err := form.ToBasicIntegration()
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	// handle write to the database
-	basic, err = app.Repo.BasicIntegration().CreateBasicIntegration(basic)
-
-	if err != nil {
-		app.handleErrorDataWrite(err, w)
-		return
-	}
-
-	app.Logger.Info().Msgf("New basic integration created: %d", basic.ID)
-
-	w.WriteHeader(http.StatusCreated)
-
-	basicExt := basic.Externalize()
-
-	if err := json.NewEncoder(w).Encode(basicExt); err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-}
-
-// HandleListProjectOAuthIntegrations lists the oauth integrations for the project
-func (app *App) HandleListProjectOAuthIntegrations(w http.ResponseWriter, r *http.Request) {
-	projID, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
-
-	if err != nil || projID == 0 {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	oauthInts, err := app.Repo.OAuthIntegration().ListOAuthIntegrationsByProjectID(uint(projID))
-
-	if err != nil {
-		app.handleErrorDataRead(err, w)
-		return
-	}
-
-	res := make([]*integrations.OAuthIntegrationExternal, 0)
-
-	for _, oauthInt := range oauthInts {
-		res = append(res, oauthInt.Externalize())
-	}
-
-	w.WriteHeader(http.StatusOK)
-
-	if err := json.NewEncoder(w).Encode(res); err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-}
-
-// verifySignature verifies a signature based on hmac protocal
-// https://docs.github.com/en/developers/webhooks-and-events/webhooks/securing-your-webhooks
-func verifySignature(secret []byte, signature string, body []byte) bool {
-	if len(signature) != 71 || !strings.HasPrefix(signature, "sha256=") {
-		return false
-	}
-
-	actual := make([]byte, 32)
-	hex.Decode(actual, []byte(signature[7:]))
-
-	computed := hmac.New(sha256.New, secret)
-	computed.Write(body)
-
-	return hmac.Equal(computed.Sum(nil), actual)
-}
-
-func (app *App) HandleGithubAppEvent(w http.ResponseWriter, r *http.Request) {
-	payload, err := ioutil.ReadAll(r.Body)
-	if err != nil {
-		app.handleErrorInternal(err, w)
-		return
-	}
-
-	// verify webhook secret
-	signature := r.Header.Get("X-Hub-Signature-256")
-
-	if !verifySignature([]byte(app.GithubAppConf.WebhookSecret), signature, payload) {
-		http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
-		return
-	}
-
-	event, err := github.ParseWebHook(github.WebHookType(r), payload)
-
-	if err != nil {
-		app.handleErrorInternal(err, w)
-		return
-	}
-
-	switch e := event.(type) {
-	case *github.InstallationEvent:
-		if *e.Action == "created" {
-			_, err := app.Repo.GithubAppInstallation().ReadGithubAppInstallationByAccountID(*e.Installation.Account.ID)
-
-			if err != nil && err == gorm.ErrRecordNotFound {
-				// insert account/installation pair into database
-				_, err := app.Repo.GithubAppInstallation().CreateGithubAppInstallation(&ints.GithubAppInstallation{
-					AccountID:      *e.Installation.Account.ID,
-					InstallationID: *e.Installation.ID,
-				})
-
-				if err != nil {
-					app.handleErrorInternal(err, w)
-				}
-
-				return
-			} else if err != nil {
-				app.handleErrorInternal(err, w)
-				return
-			}
-		}
-		if *e.Action == "deleted" {
-			err := app.Repo.GithubAppInstallation().DeleteGithubAppInstallationByAccountID(*e.Installation.Account.ID)
-
-			if err != nil {
-				app.handleErrorInternal(err, w)
-				return
-			}
-		}
-	}
-
-}
-
-// HandleGithubAppAuthorize starts the oauth2 flow for a project repo request.
-func (app *App) HandleGithubAppAuthorize(w http.ResponseWriter, r *http.Request) {
-	state := oauth.CreateRandomState()
-
-	err := app.populateOAuthSession(w, r, state, false)
-
-	if err != nil {
-		app.handleErrorDataRead(err, w)
-		return
-	}
-
-	// specify access type offline to get a refresh token
-	url := app.GithubAppConf.AuthCodeURL(state, oauth2.AccessTypeOffline)
-
-	http.Redirect(w, r, url, 302)
-}
-
-// HandleGithubAppOauthInit redirects the user to the Porter github app authorization page
-func (app *App) HandleGithubAppOauthInit(w http.ResponseWriter, r *http.Request) {
-	userID, _ := app.getUserIDFromRequest(r)
-
-	app.AnalyticsClient.Track(analytics.GithubConnectionStartTrack(
-		&analytics.GithubConnectionStartTrackOpts{
-			UserScopedTrackOpts: analytics.GetUserScopedTrackOpts(userID),
-		},
-	))
-
-	http.Redirect(w, r, app.GithubAppConf.AuthCodeURL("", oauth2.AccessTypeOffline), 302)
-}
-
-// HandleGithubAppInstall redirects the user to the Porter github app installation page
-func (app *App) HandleGithubAppInstall(w http.ResponseWriter, r *http.Request) {
-	http.Redirect(w, r, fmt.Sprintf("https://github.com/apps/%s/installations/new", app.GithubAppConf.AppName), 302)
-}
-
-// HandleListGithubAppAccessResp is the response returned by HandleListGithubAppAccess
-type HandleListGithubAppAccessResp struct {
-	HasAccess bool     `json:"has_access"`
-	LoginName string   `json:"username,omitempty"`
-	Accounts  []string `json:"accounts,omitempty"`
-}
-
-// HandleListGithubAppAccess provides basic info on if the current user is authenticated through the GitHub app
-// and what accounts/organizations their authentication has access to
-func (app *App) HandleListGithubAppAccess(w http.ResponseWriter, r *http.Request) {
-	tok, err := app.getGithubAppOauthTokenFromRequest(r)
-
-	if err != nil {
-		res := HandleListGithubAppAccessResp{
-			HasAccess: false,
-		}
-		json.NewEncoder(w).Encode(res)
-		return
-	}
-
-	client := github.NewClient(app.GithubProjectConf.Client(oauth2.NoContext, tok))
-
-	opts := &github.ListOptions{
-		PerPage: 100,
-		Page:    1,
-	}
-
-	res := HandleListGithubAppAccessResp{
-		HasAccess: true,
-	}
-
-	for {
-		orgs, pages, err := client.Organizations.List(context.Background(), "", opts)
-
-		if err != nil {
-			res := HandleListGithubAppAccessResp{
-				HasAccess: false,
-			}
-			json.NewEncoder(w).Encode(res)
-			return
-		}
-
-		for _, org := range orgs {
-			res.Accounts = append(res.Accounts, *org.Login)
-		}
-
-		if pages.NextPage == 0 {
-			break
-		}
-	}
-
-	AuthUser, _, err := client.Users.Get(context.Background(), "")
-
-	if err != nil {
-		app.handleErrorInternal(err, w)
-		return
-	}
-
-	res.LoginName = *AuthUser.Login
-
-	// check if user has app installed in their account
-	Installation, err := app.Repo.GithubAppInstallation().ReadGithubAppInstallationByAccountID(*AuthUser.ID)
-
-	if err != nil && err != gorm.ErrRecordNotFound {
-		app.handleErrorInternal(err, w)
-		return
-	}
-
-	if Installation != nil {
-		res.Accounts = append(res.Accounts, *AuthUser.Login)
-	}
-
-	sort.Strings(res.Accounts)
-
-	json.NewEncoder(w).Encode(res)
-}
-
-// getGithubAppOauthTokenFromRequest gets the oauth token from the request based on the currently
-// logged in user. Note that this authenticates as the user, rather than the installation.
-func (app *App) getGithubAppOauthTokenFromRequest(r *http.Request) (*oauth2.Token, error) {
-	userID, err := app.getUserIDFromRequest(r)
-
-	if err != nil {
-		return nil, err
-	}
-
-	user, err := app.Repo.User().ReadUser(userID)
-
-	if err != nil {
-		return nil, err
-	}
-
-	oauthInt, err := app.Repo.GithubAppOAuthIntegration().ReadGithubAppOauthIntegration(user.GithubAppIntegrationID)
-
-	if err != nil {
-		return nil, fmt.Errorf("Could not get GH app integration for user %d: %s", user.ID, err.Error())
-	}
-
-	_, _, err = oauth.GetAccessToken(oauthInt.SharedOAuthModel,
-		&app.GithubAppConf.Config,
-		oauth.MakeUpdateGithubAppOauthIntegrationFunction(oauthInt, app.Repo))
-
-	if err != nil {
-		// try again, in case the token got updated
-		oauthInt2, err := app.Repo.GithubAppOAuthIntegration().ReadGithubAppOauthIntegration(user.GithubAppIntegrationID)
-
-		if err != nil {
-			return nil, err
-		}
-
-		if oauthInt2.Expiry == oauthInt.Expiry {
-			return nil, err
-		} else {
-			oauthInt.AccessToken = oauthInt2.AccessToken
-			oauthInt.RefreshToken = oauthInt2.RefreshToken
-			oauthInt.Expiry = oauthInt2.Expiry
-		}
-	}
-
-	return &oauth2.Token{
-		AccessToken:  string(oauthInt.AccessToken),
-		RefreshToken: string(oauthInt.RefreshToken),
-		Expiry:       oauthInt.Expiry,
-		TokenType:    "Bearer",
-	}, nil
-}

+ 0 - 319
server/api/integration_handler_test.go

@@ -1,319 +0,0 @@
-package api_test
-
-// import (
-// 	"encoding/json"
-// 	"net/http"
-// 	"strings"
-// 	"testing"
-
-// 	"github.com/go-test/deep"
-// 	"github.com/porter-dev/porter/internal/forms"
-// 	ints "github.com/porter-dev/porter/internal/models/integrations"
-// )
-
-// // ------------------------- TEST TYPES AND MAIN LOOP ------------------------- //
-
-// type publicIntTest struct {
-// 	initializers []func(t *tester)
-// 	msg          string
-// 	method       string
-// 	endpoint     string
-// 	body         string
-// 	expStatus    int
-// 	expBody      string
-// 	useCookie    bool
-// 	validators   []func(c *publicIntTest, tester *tester, t *testing.T)
-// }
-
-// func testPublicIntegrationRequests(t *testing.T, tests []*publicIntTest, canQuery bool) {
-// 	for _, c := range tests {
-// 		// create a new tester
-// 		tester := newTester(canQuery)
-
-// 		// if there's an initializer, call it
-// 		for _, init := range c.initializers {
-// 			init(tester)
-// 		}
-
-// 		req, err := http.NewRequest(
-// 			c.method,
-// 			c.endpoint,
-// 			strings.NewReader(c.body),
-// 		)
-
-// 		tester.req = req
-
-// 		if c.useCookie {
-// 			req.AddCookie(tester.cookie)
-// 		}
-
-// 		if err != nil {
-// 			t.Fatal(err)
-// 		}
-
-// 		tester.execute()
-// 		rr := tester.rr
-
-// 		// first, check that the status matches
-// 		if status := rr.Code; status != c.expStatus {
-// 			t.Errorf("%s, handler returned wrong status code: got %v want %v",
-// 				c.msg, status, c.expStatus)
-// 		}
-
-// 		// if there's a validator, call it
-// 		for _, validate := range c.validators {
-// 			validate(c, tester, t)
-// 		}
-// 	}
-// }
-
-// // ------------------------- TEST FIXTURES AND FUNCTIONS  ------------------------- //
-
-// var listClusterIntegrationsTests = []*publicIntTest{
-// 	{
-// 		initializers: []func(t *tester){
-// 			initUserDefault,
-// 		},
-// 		msg:       "List cluster integrations",
-// 		method:    "GET",
-// 		endpoint:  "/api/integrations/cluster",
-// 		body:      ``,
-// 		expStatus: http.StatusOK,
-// 		expBody:   `[{"auth_mechanism":"gcp","category":"cluster","service":"gke"},{"auth_mechanism":"aws","category":"cluster","service":"eks"},{"auth_mechanism":"kube","category":"cluster","service":"kube"}]`,
-// 		useCookie: true,
-// 		validators: []func(c *publicIntTest, tester *tester, t *testing.T){
-// 			publicIntBodyValidator,
-// 		},
-// 	},
-// }
-
-// func TestHandleListClusterIntegrations(t *testing.T) {
-// 	testPublicIntegrationRequests(t, listClusterIntegrationsTests, true)
-// }
-
-// var listRegistryIntegrationsTests = []*publicIntTest{
-// 	{
-// 		initializers: []func(t *tester){
-// 			initUserDefault,
-// 		},
-// 		msg:       "List registry integrations",
-// 		method:    "GET",
-// 		endpoint:  "/api/integrations/registry",
-// 		body:      ``,
-// 		expStatus: http.StatusOK,
-// 		expBody:   `[{"auth_mechanism":"gcp","category":"registry","service":"gcr"},{"auth_mechanism":"aws","category":"registry","service":"ecr"},{"auth_mechanism":"oauth","category":"registry","service":"docker"}]`,
-// 		useCookie: true,
-// 		validators: []func(c *publicIntTest, tester *tester, t *testing.T){
-// 			publicIntBodyValidator,
-// 		},
-// 	},
-// }
-
-// func TestHandleListRegistryIntegrations(t *testing.T) {
-// 	testPublicIntegrationRequests(t, listRegistryIntegrationsTests, true)
-// }
-
-// var listHelmRepoIntegrationsTest = []*publicIntTest{
-// 	{
-// 		initializers: []func(t *tester){
-// 			initUserDefault,
-// 		},
-// 		msg:       "List Helm repo integrations",
-// 		method:    "GET",
-// 		endpoint:  "/api/integrations/helm",
-// 		body:      ``,
-// 		expStatus: http.StatusOK,
-// 		expBody:   `[{"auth_mechanism":"basic","category":"helm","service":"helm"},{"auth_mechanism":"gcp","category":"helm","service":"gcs"},{"auth_mechanism":"aws","category":"helm","service":"s3"}]`,
-// 		useCookie: true,
-// 		validators: []func(c *publicIntTest, tester *tester, t *testing.T){
-// 			publicIntBodyValidator,
-// 		},
-// 	},
-// }
-
-// func TestHandleListHelmRepoIntegrations(t *testing.T) {
-// 	testPublicIntegrationRequests(t, listHelmRepoIntegrationsTest, true)
-// }
-
-// var listRepoIntegrationsTests = []*publicIntTest{
-// 	{
-// 		initializers: []func(t *tester){
-// 			initUserDefault,
-// 		},
-// 		msg:       "List repo integrations",
-// 		method:    "GET",
-// 		endpoint:  "/api/integrations/repo",
-// 		body:      ``,
-// 		expStatus: http.StatusOK,
-// 		expBody:   `[{"auth_mechanism":"oauth","category":"repo","service":"github"}]`,
-// 		useCookie: true,
-// 		validators: []func(c *publicIntTest, tester *tester, t *testing.T){
-// 			publicIntBodyValidator,
-// 		},
-// 	},
-// }
-
-// func TestHandleListRepoIntegrations(t *testing.T) {
-// 	testPublicIntegrationRequests(t, listRepoIntegrationsTests, true)
-// }
-
-// var createGCPIntegrationTests = []*publicIntTest{
-// 	{
-// 		initializers: []func(t *tester){
-// 			initUserDefault,
-// 			initProject,
-// 		},
-// 		msg:      "Create GCP Integration",
-// 		method:   "POST",
-// 		endpoint: "/api/projects/1/integrations/gcp",
-// 		body: `{
-// 			"gcp_key_data": "yoooo"
-// 		}`,
-// 		expStatus: http.StatusCreated,
-// 		expBody:   `{"id":1,"user_id":1,"project_id":1,"gcp-project-id":"","gcp-user-email":""}`,
-// 		useCookie: true,
-// 		validators: []func(c *publicIntTest, tester *tester, t *testing.T){
-// 			gcpIntBodyValidator,
-// 		},
-// 	},
-// }
-
-// func TestHandleCreateGCPIntegration(t *testing.T) {
-// 	testPublicIntegrationRequests(t, createGCPIntegrationTests, true)
-// }
-
-// var createAWSIntegrationTests = []*publicIntTest{
-// 	{
-// 		initializers: []func(t *tester){
-// 			initUserDefault,
-// 			initProject,
-// 		},
-// 		msg:      "Create AWS Integration",
-// 		method:   "POST",
-// 		endpoint: "/api/projects/1/integrations/aws",
-// 		body: `{
-// 			"aws_cluster_id": "cluster-id-0",
-// 			"aws_access_key_id": "accesskey",
-// 			"aws_secret_access_key": "secretkey"
-// 		}`,
-// 		expStatus: http.StatusCreated,
-// 		expBody:   `{"id":1,"user_id":1,"project_id":1,"aws-entity-id":"","aws-caller-id":""}`,
-// 		useCookie: true,
-// 		validators: []func(c *publicIntTest, tester *tester, t *testing.T){
-// 			awsIntBodyValidator,
-// 		},
-// 	},
-// }
-
-// func TestHandleCreateAWSIntegration(t *testing.T) {
-// 	testPublicIntegrationRequests(t, createGCPIntegrationTests, true)
-// }
-
-// var createBasicIntegrationTests = []*publicIntTest{
-// 	{
-// 		initializers: []func(t *tester){
-// 			initUserDefault,
-// 			initProject,
-// 		},
-// 		msg:      "Create basic integration",
-// 		method:   "POST",
-// 		endpoint: "/api/projects/1/integrations/basic",
-// 		body: `{
-// 			"username": "username",
-// 			"password": "password"
-// 		}`,
-// 		expStatus: http.StatusCreated,
-// 		expBody:   `{"id":1,"user_id":1,"project_id":1}`,
-// 		useCookie: true,
-// 		validators: []func(c *publicIntTest, tester *tester, t *testing.T){
-// 			basicIntBodyValidator,
-// 		},
-// 	},
-// }
-
-// func TestHandleCreateBasicIntegration(t *testing.T) {
-// 	testPublicIntegrationRequests(t, createBasicIntegrationTests, true)
-// }
-
-// // ------------------------- INITIALIZERS AND VALIDATORS ------------------------- //
-
-// func initAWSIntegration(tester *tester) {
-// 	proj, _ := tester.repo.Project().ReadProject(1)
-
-// 	form := &forms.CreateAWSIntegrationForm{
-// 		ProjectID: proj.ID,
-// 		UserID:    1,
-// 	}
-
-// 	// convert the form to a ServiceAccountCandidate
-// 	awsInt, _ := form.ToAWSIntegration()
-
-// 	tester.repo.AWSIntegration().CreateAWSIntegration(awsInt)
-// }
-
-// func initBasicIntegration(tester *tester) {
-// 	proj, _ := tester.repo.Project().ReadProject(1)
-
-// 	basicInt := &ints.BasicIntegration{
-// 		ProjectID: proj.ID,
-// 		UserID:    1,
-// 	}
-
-// 	tester.repo.BasicIntegration().CreateBasicIntegration(basicInt)
-// }
-
-// func publicIntBodyValidator(c *publicIntTest, tester *tester, t *testing.T) {
-// 	gotBody := make([]*ints.PorterIntegration, 0)
-// 	expBody := make([]*ints.PorterIntegration, 0)
-
-// 	bytes := tester.rr.Body.Bytes()
-
-// 	json.Unmarshal(bytes, &gotBody)
-// 	json.Unmarshal([]byte(c.expBody), &expBody)
-
-// 	if diff := deep.Equal(gotBody, expBody); diff != nil {
-// 		t.Errorf("handler returned wrong body:\n")
-// 		t.Error(diff)
-// 	}
-// }
-
-// func gcpIntBodyValidator(c *publicIntTest, tester *tester, t *testing.T) {
-// 	gotBody := &ints.GCPIntegration{}
-// 	expBody := &ints.GCPIntegration{}
-
-// 	json.Unmarshal(tester.rr.Body.Bytes(), &gotBody)
-// 	json.Unmarshal([]byte(c.expBody), &expBody)
-
-// 	if diff := deep.Equal(gotBody, expBody); diff != nil {
-// 		t.Errorf("handler returned wrong body:\n")
-// 		t.Error(diff)
-// 	}
-// }
-
-// func awsIntBodyValidator(c *publicIntTest, tester *tester, t *testing.T) {
-// 	gotBody := &ints.AWSIntegration{}
-// 	expBody := &ints.AWSIntegration{}
-
-// 	json.Unmarshal(tester.rr.Body.Bytes(), &gotBody)
-// 	json.Unmarshal([]byte(c.expBody), &expBody)
-
-// 	if diff := deep.Equal(gotBody, expBody); diff != nil {
-// 		t.Errorf("handler returned wrong body:\n")
-// 		t.Error(diff)
-// 	}
-// }
-
-// func basicIntBodyValidator(c *publicIntTest, tester *tester, t *testing.T) {
-// 	gotBody := &ints.BasicIntegration{}
-// 	expBody := &ints.BasicIntegration{}
-
-// 	bytes := tester.rr.Body.Bytes()
-
-// 	json.Unmarshal(bytes, &gotBody)
-// 	json.Unmarshal([]byte(c.expBody), &expBody)
-
-// 	if diff := deep.Equal(gotBody, expBody); diff != nil {
-// 		t.Errorf("handler returned wrong body:\n")
-// 		t.Error(diff)
-// 	}
-// }

+ 0 - 298
server/api/invite_handler.go

@@ -1,298 +0,0 @@
-package api
-
-import (
-	"encoding/json"
-	"fmt"
-	"net/http"
-	"net/url"
-	"strconv"
-
-	"github.com/go-chi/chi"
-	"github.com/porter-dev/porter/api/types"
-	"github.com/porter-dev/porter/internal/forms"
-	"github.com/porter-dev/porter/internal/models"
-	"github.com/porter-dev/porter/internal/notifier"
-)
-
-// HandleCreateInvite creates a new invite for a project
-func (app *App) HandleCreateInvite(w http.ResponseWriter, r *http.Request) {
-	projID, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
-
-	if err != nil || projID == 0 {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	form := &forms.CreateInvite{
-		ProjectID: uint(projID),
-	}
-
-	// decode from JSON to form value
-	if err := json.NewDecoder(r.Body).Decode(form); err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	// validate the form
-	if err := app.validator.Struct(form); err != nil {
-		app.handleErrorFormValidation(err, ErrProjectValidateFields, w)
-		return
-	}
-
-	// convert the form to an invite
-	invite, err := form.ToInvite()
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	// handle write to the database
-	invite, err = app.Repo.Invite().CreateInvite(invite)
-
-	if err != nil {
-		app.handleErrorDataWrite(err, w)
-		return
-	}
-
-	app.Logger.Info().Msgf("New invite created: %d", invite.ID)
-
-	w.WriteHeader(http.StatusCreated)
-
-	inviteExt := invite.Externalize()
-
-	if err := json.NewEncoder(w).Encode(inviteExt); err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	// send invite email
-	project, err := app.Repo.Project().ReadProject(uint(projID))
-
-	if err != nil {
-		return
-	}
-
-	userID, err := app.getUserIDFromRequest(r)
-
-	if err != nil {
-		return
-	}
-
-	user, err := app.Repo.User().ReadUser(userID)
-
-	if err != nil {
-		return
-	}
-
-	app.notifier.SendProjectInviteEmail(
-		&notifier.SendProjectInviteEmailOpts{
-			InviteeEmail:      form.Email,
-			URL:               fmt.Sprintf("%s/api/projects/%d/invites/%s", app.ServerConf.ServerURL, projID, invite.Token),
-			Project:           project.Name,
-			ProjectOwnerEmail: user.Email,
-		},
-	)
-}
-
-// HandleUpdateInviteRole updates the role for a pending invitation
-func (app *App) HandleUpdateInviteRole(w http.ResponseWriter, r *http.Request) {
-	id, err := strconv.ParseUint(chi.URLParam(r, "invite_id"), 0, 64)
-
-	if err != nil || id == 0 {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	invite, err := app.Repo.Invite().ReadInvite(uint(id))
-
-	if err != nil {
-		app.handleErrorRead(err, ErrProjectDataRead, w)
-		return
-	}
-
-	form := &forms.UpdateProjectRoleForm{}
-
-	// decode from JSON to form value
-	if err := json.NewDecoder(r.Body).Decode(form); err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	invite.Kind = form.Kind
-
-	invite, err = app.Repo.Invite().UpdateInvite(invite)
-
-	if err != nil {
-		app.handleErrorRead(err, ErrProjectDataRead, w)
-		return
-	}
-
-	w.WriteHeader(http.StatusOK)
-}
-
-// HandleAcceptInvite accepts an invite to a new project: if successful, a new role
-// is created for that user in the project
-func (app *App) HandleAcceptInvite(w http.ResponseWriter, r *http.Request) {
-	session, err := app.Store.Get(r, app.ServerConf.CookieName)
-
-	if err != nil {
-		acceptInviteError(w, r)
-		return
-	}
-
-	userID, _ := session.Values["user_id"].(uint)
-
-	user, err := app.Repo.User().ReadUser(userID)
-
-	if err != nil {
-		acceptInviteError(w, r)
-		return
-	}
-
-	token := chi.URLParam(r, "token")
-
-	if token == "" {
-		acceptInviteError(w, r)
-		return
-	}
-
-	projID, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
-
-	if err != nil || projID == 0 {
-		acceptInviteError(w, r)
-		return
-	}
-
-	invite, err := app.Repo.Invite().ReadInviteByToken(token)
-
-	if err != nil || invite.ProjectID != uint(projID) {
-		vals := url.Values{}
-		vals.Add("error", "Invalid invite token")
-		http.Redirect(w, r, fmt.Sprintf("/dashboard?%s", vals.Encode()), 302)
-
-		return
-	}
-
-	// check that the invite has not expired and has not been accepted
-	if invite.IsExpired() || invite.IsAccepted() {
-		vals := url.Values{}
-		vals.Add("error", "Invite has expired")
-		http.Redirect(w, r, fmt.Sprintf("/dashboard?%s", vals.Encode()), 302)
-
-		return
-	}
-
-	// check that the invite email matches the user's email
-	if user.Email != invite.Email {
-		vals := url.Values{}
-		vals.Add("error", "Wrong email for invite")
-		http.Redirect(w, r, fmt.Sprintf("/dashboard?%s", vals.Encode()), 302)
-
-		return
-	}
-
-	// create a new role for the user in the project
-	projModel, err := app.Repo.Project().ReadProject(uint(projID))
-
-	if err != nil {
-		acceptInviteError(w, r)
-		return
-	}
-
-	kind := invite.Kind
-
-	if kind == "" {
-		kind = models.RoleDeveloper
-	}
-
-	// create a new Role with the user as the admin
-	_, err = app.Repo.Project().CreateProjectRole(projModel, &models.Role{
-		Role: types.Role{
-			UserID:    userID,
-			ProjectID: uint(projID),
-			Kind:      types.RoleKind(kind),
-		},
-	})
-
-	if err != nil {
-		acceptInviteError(w, r)
-		return
-	}
-
-	// update the invite
-	invite.UserID = userID
-
-	_, err = app.Repo.Invite().UpdateInvite(invite)
-
-	if err != nil {
-		acceptInviteError(w, r)
-		return
-	}
-
-	http.Redirect(w, r, "/dashboard", 302)
-	return
-}
-
-func acceptInviteError(w http.ResponseWriter, r *http.Request) {
-	vals := url.Values{}
-	vals.Add("error", "could not accept invite")
-	http.Redirect(w, r, fmt.Sprintf("/dashboard?%s", vals.Encode()), 302)
-	return
-}
-
-// HandleListProjectInvites returns a list of invites for a project
-func (app *App) HandleListProjectInvites(w http.ResponseWriter, r *http.Request) {
-	projID, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
-
-	if err != nil || projID == 0 {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	invites, err := app.Repo.Invite().ListInvitesByProjectID(uint(projID))
-
-	if err != nil {
-		app.handleErrorRead(err, ErrProjectDataRead, w)
-		return
-	}
-
-	extInvites := make([]*models.InviteExternal, 0)
-
-	for _, invite := range invites {
-		extInvites = append(extInvites, invite.Externalize())
-	}
-
-	w.WriteHeader(http.StatusOK)
-
-	if err := json.NewEncoder(w).Encode(extInvites); err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-}
-
-// HandleDeleteProjectInvite handles the deletion of an Invite via the invite ID
-func (app *App) HandleDeleteProjectInvite(w http.ResponseWriter, r *http.Request) {
-	id, err := strconv.ParseUint(chi.URLParam(r, "invite_id"), 0, 64)
-
-	if err != nil || id == 0 {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	invite, err := app.Repo.Invite().ReadInvite(uint(id))
-
-	if err != nil {
-		app.handleErrorRead(err, ErrProjectDataRead, w)
-		return
-	}
-
-	err = app.Repo.Invite().DeleteInvite(invite)
-
-	if err != nil {
-		app.handleErrorRead(err, ErrProjectDataRead, w)
-		return
-	}
-
-	w.WriteHeader(http.StatusOK)
-}

+ 0 - 297
server/api/invite_handler_test.go

@@ -1,297 +0,0 @@
-package api_test
-
-// import (
-// 	"encoding/json"
-// 	"net/http"
-// 	"strings"
-// 	"testing"
-// 	"time"
-
-// 	"github.com/go-test/deep"
-// 	"github.com/porter-dev/porter/internal/models"
-// )
-
-// // ------------------------- TEST TYPES AND MAIN LOOP ------------------------- //
-
-// type inviteTest struct {
-// 	initializers []func(t *tester)
-// 	msg          string
-// 	method       string
-// 	endpoint     string
-// 	body         string
-// 	expStatus    int
-// 	expBody      string
-// 	useCookie    bool
-// 	validators   []func(c *inviteTest, tester *tester, t *testing.T)
-// }
-
-// func testInviteRequests(t *testing.T, tests []*inviteTest, canQuery bool) {
-// 	for _, c := range tests {
-// 		// create a new tester
-// 		tester := newTester(canQuery)
-
-// 		// if there's an initializer, call it
-// 		for _, init := range c.initializers {
-// 			init(tester)
-// 		}
-
-// 		req, err := http.NewRequest(
-// 			c.method,
-// 			c.endpoint,
-// 			strings.NewReader(c.body),
-// 		)
-
-// 		tester.req = req
-
-// 		if c.useCookie {
-// 			req.AddCookie(tester.cookie)
-// 		}
-
-// 		if err != nil {
-// 			t.Fatal(err)
-// 		}
-
-// 		tester.execute()
-// 		rr := tester.rr
-
-// 		// first, check that the status matches
-// 		if status := rr.Code; status != c.expStatus {
-// 			t.Errorf("%s, handler returned wrong status code: got %v want %v",
-// 				c.msg, status, c.expStatus)
-// 		}
-
-// 		// if there's a validator, call it
-// 		for _, validate := range c.validators {
-// 			validate(c, tester, t)
-// 		}
-// 	}
-// }
-
-// // ------------------------- TEST FIXTURES AND FUNCTIONS  ------------------------- //
-
-// var createInviteTests = []*inviteTest{
-// 	{
-// 		initializers: []func(t *tester){
-// 			initUserDefault,
-// 			initProject,
-// 		},
-// 		msg:       "Create invite",
-// 		method:    "POST",
-// 		endpoint:  "/api/projects/1/invites",
-// 		body:      `{"email":"test@test.it"}`,
-// 		expStatus: http.StatusCreated,
-// 		expBody:   `{"id":1,"expired":false,"email":"test@test.it","accepted":false}`,
-// 		useCookie: true,
-// 		validators: []func(c *inviteTest, tester *tester, t *testing.T){
-// 			func(c *inviteTest, tester *tester, t *testing.T) {
-// 				// manually read the invite to get the expected token
-// 				invite, _ := tester.repo.Invite().ReadInvite(1)
-
-// 				gotBody := &models.InviteExternal{}
-// 				expBody := &models.InviteExternal{
-// 					Token: invite.Token,
-// 				}
-
-// 				json.Unmarshal(tester.rr.Body.Bytes(), &gotBody)
-// 				json.Unmarshal([]byte(c.expBody), &expBody)
-
-// 				if diff := deep.Equal(gotBody, expBody); diff != nil {
-// 					t.Errorf("handler returned wrong body:\n")
-// 					t.Error(diff)
-// 				}
-// 			},
-// 		},
-// 	},
-// }
-
-// func TestHandleCreateInvite(t *testing.T) {
-// 	testInviteRequests(t, createInviteTests, true)
-// }
-
-// var listInvitesTest = []*inviteTest{
-// 	{
-// 		initializers: []func(t *tester){
-// 			initUserDefault,
-// 			initProject,
-// 			initInvite,
-// 		},
-// 		msg:       "List invites",
-// 		method:    "GET",
-// 		endpoint:  "/api/projects/1/invites",
-// 		body:      ``,
-// 		expStatus: http.StatusOK,
-// 		expBody:   `[{"id":1,"expired":false,"email":"test@test.it","accepted":false}]`,
-// 		useCookie: true,
-// 		validators: []func(c *inviteTest, tester *tester, t *testing.T){
-// 			func(c *inviteTest, tester *tester, t *testing.T) {
-// 				// manually read the invite to get the expected token
-// 				invite, _ := tester.repo.Invite().ReadInvite(1)
-
-// 				gotBody := []*models.InviteExternal{}
-// 				expBody := []*models.InviteExternal{}
-
-// 				json.Unmarshal(tester.rr.Body.Bytes(), &gotBody)
-// 				json.Unmarshal([]byte(c.expBody), &expBody)
-
-// 				expBody[0].Token = invite.Token
-
-// 				if diff := deep.Equal(gotBody, expBody); diff != nil {
-// 					t.Errorf("handler returned wrong body:\n")
-// 					t.Error(diff)
-// 				}
-// 			},
-// 		},
-// 	},
-// }
-
-// func TestHandleListInvites(t *testing.T) {
-// 	testInviteRequests(t, listInvitesTest, true)
-// }
-
-// var acceptInviteTests = []*inviteTest{
-// 	{
-// 		initializers: []func(t *tester){
-// 			initUserDefault,
-// 			initUserAlt,
-// 			initProject,
-// 			initInvite,
-// 		},
-// 		msg:       "Accept invite",
-// 		method:    "GET",
-// 		endpoint:  "/api/projects/1/invites/abcd",
-// 		body:      ``,
-// 		expStatus: http.StatusFound,
-// 		expBody:   ``,
-// 		useCookie: true,
-// 		validators: []func(c *inviteTest, tester *tester, t *testing.T){
-// 			func(c *inviteTest, tester *tester, t *testing.T) {
-// 				user, err := tester.repo.User().ReadUserByEmail("test@test.it")
-
-// 				if err != nil {
-// 					t.Fatalf("%v\n", err)
-// 				}
-
-// 				projects, err := tester.repo.Project().ListProjectsByUserID(user.ID)
-
-// 				if len(projects) != 1 {
-// 					t.Fatalf("length of projects not 1\n")
-// 				}
-
-// 				if projects[0].ID != 1 {
-// 					t.Fatalf("project id was not 1\n")
-// 				}
-
-// 				if projects[0].Name != "project-test" {
-// 					t.Fatalf("project was not project-test\n")
-// 				}
-// 			},
-// 		},
-// 	},
-// 	{
-// 		initializers: []func(t *tester){
-// 			initUserDefault,
-// 			initUserAlt,
-// 			initProject,
-// 			initInvite,
-// 		},
-// 		msg:       "Accept invite wrong token",
-// 		method:    "GET",
-// 		endpoint:  "/api/projects/1/invites/abcd1",
-// 		body:      ``,
-// 		expStatus: http.StatusFound,
-// 		expBody:   ``,
-// 		useCookie: true,
-// 		validators: []func(c *inviteTest, tester *tester, t *testing.T){
-// 			func(c *inviteTest, tester *tester, t *testing.T) {
-// 				expRes := "/dashboard?error=Invalid+invite+token"
-
-// 				if expRes != tester.rr.HeaderMap.Get("Location") {
-// 					t.Fatalf("Redirect location not correct: expected %v, got %v\n", expRes, tester.rr.HeaderMap.Get("Location"))
-// 				}
-// 			},
-// 		},
-// 	},
-// 	{
-// 		initializers: []func(t *tester){
-// 			initUserDefault,
-// 			initProject,
-// 			initInvite,
-// 		},
-// 		msg:       "Accept invite wrong user",
-// 		method:    "GET",
-// 		endpoint:  "/api/projects/1/invites/abcd",
-// 		body:      ``,
-// 		expStatus: http.StatusFound,
-// 		expBody:   ``,
-// 		useCookie: true,
-// 		validators: []func(c *inviteTest, tester *tester, t *testing.T){
-// 			func(c *inviteTest, tester *tester, t *testing.T) {
-// 				expRes := "/dashboard?error=Wrong+email+for+invite"
-
-// 				if expRes != tester.rr.HeaderMap.Get("Location") {
-// 					t.Fatalf("Redirect location not correct: expected %v, got %v\n", expRes, tester.rr.HeaderMap.Get("Location"))
-// 				}
-// 			},
-// 		},
-// 	},
-// 	{
-// 		initializers: []func(t *tester){
-// 			initUserDefault,
-// 			initUserAlt,
-// 			initProject,
-// 			initInviteExpiredToken,
-// 		},
-// 		msg:       "Accept invite expired token",
-// 		method:    "GET",
-// 		endpoint:  "/api/projects/1/invites/abcd",
-// 		body:      ``,
-// 		expStatus: http.StatusFound,
-// 		expBody:   ``,
-// 		useCookie: true,
-// 		validators: []func(c *inviteTest, tester *tester, t *testing.T){
-// 			func(c *inviteTest, tester *tester, t *testing.T) {
-// 				expRes := "/dashboard?error=Invite+has+expired"
-
-// 				if expRes != tester.rr.HeaderMap.Get("Location") {
-// 					t.Fatalf("Redirect location not correct: expected %v, got %v\n", expRes, tester.rr.HeaderMap.Get("Location"))
-// 				}
-// 			},
-// 		},
-// 	},
-// }
-
-// func TestHandleAcceptInvite(t *testing.T) {
-// 	testInviteRequests(t, acceptInviteTests, true)
-// }
-
-// // ------------------------- INITIALIZERS AND VALIDATORS ------------------------- //
-
-// func initInvite(tester *tester) {
-// 	proj, _ := tester.repo.Project().ReadProject(1)
-
-// 	expiry := time.Now().Add(24 * time.Hour)
-
-// 	invite := &models.Invite{
-// 		Token:     "abcd",
-// 		Expiry:    &expiry,
-// 		Email:     "test@test.it",
-// 		ProjectID: proj.Model.ID,
-// 	}
-
-// 	tester.repo.Invite().CreateInvite(invite)
-// }
-
-// func initInviteExpiredToken(tester *tester) {
-// 	proj, _ := tester.repo.Project().ReadProject(1)
-
-// 	expiry := time.Now().Add(-1 * time.Hour)
-
-// 	invite := &models.Invite{
-// 		Token:     "abcd",
-// 		Expiry:    &expiry,
-// 		Email:     "belanger@getporter.dev",
-// 		ProjectID: proj.Model.ID,
-// 	}
-
-// 	tester.repo.Invite().CreateInvite(invite)
-// }

+ 0 - 1529
server/api/k8s_handler.go

@@ -1,1529 +0,0 @@
-package api
-
-import (
-	"encoding/json"
-	"fmt"
-	"net/http"
-	"net/url"
-	"strconv"
-
-	"github.com/go-chi/chi"
-	"github.com/gorilla/schema"
-	"github.com/gorilla/websocket"
-	"github.com/porter-dev/porter/internal/forms"
-	"github.com/porter-dev/porter/internal/kubernetes"
-	"github.com/porter-dev/porter/internal/kubernetes/nodes"
-	"github.com/porter-dev/porter/internal/kubernetes/prometheus"
-	v1 "k8s.io/api/core/v1"
-	"k8s.io/client-go/tools/clientcmd"
-)
-
-// Enumeration of k8s API error codes, represented as int64
-const (
-	ErrK8sDecode ErrorCode = iota + 600
-	ErrK8sValidate
-	ErrEnvDecode
-)
-
-var upgrader = websocket.Upgrader{
-	ReadBufferSize:  1024,
-	WriteBufferSize: 1024,
-}
-
-// HandleListNamespaces retrieves a list of namespaces
-func (app *App) HandleListNamespaces(w http.ResponseWriter, r *http.Request) {
-	vals, err := url.ParseQuery(r.URL.RawQuery)
-
-	if err != nil {
-		http.Error(w, err.Error(), http.StatusInternalServerError)
-		return
-	}
-
-	// get the filter options
-	form := &forms.K8sForm{
-		OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
-			Repo:              app.Repo,
-			DigitalOceanOAuth: app.DOConf,
-		},
-	}
-
-	form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster())
-
-	// validate the form
-	if err := app.validator.Struct(form); err != nil {
-		app.handleErrorFormValidation(err, ErrK8sValidate, w)
-		return
-	}
-
-	// create a new agent
-	var agent *kubernetes.Agent
-
-	if app.ServerConf.IsTesting {
-		agent = app.TestAgents.K8sAgent
-	} else {
-		agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
-	}
-
-	namespaces, err := agent.ListNamespaces()
-
-	if err != nil {
-		app.handleErrorDataRead(err, w)
-		return
-	}
-
-	if err := json.NewEncoder(w).Encode(namespaces); err != nil {
-		app.handleErrorFormDecoding(err, ErrK8sDecode, w)
-		return
-	}
-}
-
-// HandleCreateNamespace creates a new namespace given the name.
-func (app *App) HandleCreateNamespace(w http.ResponseWriter, r *http.Request) {
-	vals, err := url.ParseQuery(r.URL.RawQuery)
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
-		return
-	}
-
-	// get the filter options
-	form := &forms.K8sForm{
-		OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
-			Repo:              app.Repo,
-			DigitalOceanOAuth: app.DOConf,
-		},
-	}
-
-	form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster())
-
-	// validate the form
-	if err := app.validator.Struct(form); err != nil {
-		app.handleErrorFormValidation(err, ErrK8sValidate, w)
-		return
-	}
-
-	// create a new agent
-	var agent *kubernetes.Agent
-
-	if app.ServerConf.IsTesting {
-		agent = app.TestAgents.K8sAgent
-	} else {
-		agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
-	}
-
-	ns := &forms.NamespaceForm{}
-
-	if err := json.NewDecoder(r.Body).Decode(ns); err != nil {
-		app.handleErrorFormDecoding(err, ErrUserDecode, w)
-		return
-	}
-
-	namespace, err := agent.CreateNamespace(ns.Name)
-
-	if err != nil {
-		app.handleErrorInternal(err, w)
-		return
-	}
-
-	if err := json.NewEncoder(w).Encode(namespace); err != nil {
-		app.handleErrorFormDecoding(err, ErrK8sDecode, w)
-		return
-	}
-
-	w.WriteHeader(http.StatusOK)
-	return
-}
-
-// HandleDeleteNamespace deletes a namespace given the name.
-func (app *App) HandleDeleteNamespace(w http.ResponseWriter, r *http.Request) {
-	vals, err := url.ParseQuery(r.URL.RawQuery)
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
-		return
-	}
-
-	// get the filter options
-	form := &forms.K8sForm{
-		OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
-			Repo:              app.Repo,
-			DigitalOceanOAuth: app.DOConf,
-		},
-	}
-
-	form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster())
-
-	// validate the form
-	if err := app.validator.Struct(form); err != nil {
-		app.handleErrorFormValidation(err, ErrK8sValidate, w)
-		return
-	}
-
-	// create a new agent
-	var agent *kubernetes.Agent
-
-	if app.ServerConf.IsTesting {
-		agent = app.TestAgents.K8sAgent
-	} else {
-		agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
-	}
-
-	if err != nil {
-		app.handleErrorInternal(err, w)
-		return
-	}
-
-	namespace := &forms.NamespaceForm{
-		Name: vals.Get("name"),
-	}
-
-	err = agent.DeleteNamespace(namespace.Name)
-
-	if err != nil {
-		app.handleErrorInternal(err, w)
-		return
-	}
-
-	w.WriteHeader(http.StatusOK)
-	return
-}
-
-// HandleListPodEvents retrieves all events tied to a pod.
-func (app *App) HandleListPodEvents(w http.ResponseWriter, r *http.Request) {
-	vals, err := url.ParseQuery(r.URL.RawQuery)
-
-	// get path parameters
-	namespace := chi.URLParam(r, "namespace")
-	name := chi.URLParam(r, "name")
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
-		return
-	}
-
-	// get the filter options
-	form := &forms.K8sForm{
-		OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
-			Repo:              app.Repo,
-			DigitalOceanOAuth: app.DOConf,
-		},
-	}
-
-	form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster())
-
-	// validate the form
-	if err := app.validator.Struct(form); err != nil {
-		app.handleErrorFormValidation(err, ErrK8sValidate, w)
-		return
-	}
-
-	// create a new agent
-	var agent *kubernetes.Agent
-
-	if app.ServerConf.IsTesting {
-		agent = app.TestAgents.K8sAgent
-	} else {
-		agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
-	}
-
-	events, err := agent.ListEvents(name, namespace)
-
-	if err != nil {
-		app.handleErrorDataRead(err, w)
-		return
-	}
-
-	if err := json.NewEncoder(w).Encode(events); err != nil {
-		app.handleErrorFormDecoding(err, ErrK8sDecode, w)
-		return
-	}
-}
-
-func createConfigMap(agent *kubernetes.Agent, configMap *forms.ConfigMapForm) (*v1.ConfigMap, error) {
-	secretData := make(map[string][]byte)
-
-	for key, rawValue := range configMap.SecretEnvVariables {
-		// encodedValue := base64.StdEncoding.EncodeToString([]byte(rawValue))
-
-		// if err != nil {
-		// 	app.handleErrorInternal(err, w)
-		// 	return
-		// }
-
-		secretData[key] = []byte(rawValue)
-	}
-
-	// create secret first
-	if _, err := agent.CreateLinkedSecret(configMap.Name, configMap.Namespace, configMap.Name, secretData); err != nil {
-		return nil, err
-	}
-
-	// add all secret env variables to configmap with value PORTERSECRET_${configmap_name}
-	for key := range configMap.SecretEnvVariables {
-		configMap.EnvVariables[key] = fmt.Sprintf("PORTERSECRET_%s", configMap.Name)
-	}
-
-	return agent.CreateConfigMap(configMap.Name, configMap.Namespace, configMap.EnvVariables)
-}
-
-// HandleCreateConfigMap creates a configmap (and secret) given the name, namespace and variables.
-func (app *App) HandleCreateConfigMap(w http.ResponseWriter, r *http.Request) {
-	vals, err := url.ParseQuery(r.URL.RawQuery)
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
-		return
-	}
-
-	// get the filter options
-	form := &forms.K8sForm{
-		OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
-			Repo:              app.Repo,
-			DigitalOceanOAuth: app.DOConf,
-		},
-	}
-
-	form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster())
-
-	// validate the form
-	if err := app.validator.Struct(form); err != nil {
-		app.handleErrorFormValidation(err, ErrK8sValidate, w)
-		return
-	}
-
-	// create a new agent
-	var agent *kubernetes.Agent
-
-	if app.ServerConf.IsTesting {
-		agent = app.TestAgents.K8sAgent
-	} else {
-		agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
-	}
-
-	configMapForm := &forms.ConfigMapForm{}
-
-	if err := json.NewDecoder(r.Body).Decode(configMapForm); err != nil {
-		app.handleErrorFormDecoding(err, ErrUserDecode, w)
-		return
-	}
-
-	if _, err := createConfigMap(agent, configMapForm); err != nil {
-		app.handleErrorInternal(err, w)
-		return
-	}
-
-	if err := json.NewEncoder(w).Encode(configMapForm); err != nil {
-		app.handleErrorFormDecoding(err, ErrEnvDecode, w)
-		return
-	}
-
-	w.WriteHeader(http.StatusOK)
-	return
-}
-
-// HandleListConfigMaps lists all configmaps in a namespace.
-func (app *App) HandleListConfigMaps(w http.ResponseWriter, r *http.Request) {
-	vals, err := url.ParseQuery(r.URL.RawQuery)
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
-		return
-	}
-
-	// get the filter options
-	form := &forms.K8sForm{
-		OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
-			Repo:              app.Repo,
-			DigitalOceanOAuth: app.DOConf,
-		},
-	}
-
-	form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster())
-
-	// validate the form
-	if err := app.validator.Struct(form); err != nil {
-		app.handleErrorFormValidation(err, ErrK8sValidate, w)
-		return
-	}
-
-	// create a new agent
-	var agent *kubernetes.Agent
-
-	if app.ServerConf.IsTesting {
-		agent = app.TestAgents.K8sAgent
-	} else {
-		agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
-	}
-
-	configMaps, err := agent.ListConfigMaps(vals["namespace"][0])
-
-	if err != nil {
-		app.handleErrorInternal(err, w)
-		return
-	}
-
-	if err := json.NewEncoder(w).Encode(configMaps); err != nil {
-		app.handleErrorFormDecoding(err, ErrEnvDecode, w)
-		return
-	}
-
-	w.WriteHeader(http.StatusOK)
-	return
-}
-
-// HandleGetConfigMap retreives the configmap given the name and namespace.
-func (app *App) HandleGetConfigMap(w http.ResponseWriter, r *http.Request) {
-	vals, err := url.ParseQuery(r.URL.RawQuery)
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
-		return
-	}
-
-	// get the filter options
-	form := &forms.K8sForm{
-		OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
-			Repo:              app.Repo,
-			DigitalOceanOAuth: app.DOConf,
-		},
-	}
-
-	form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster())
-
-	// validate the form
-	if err := app.validator.Struct(form); err != nil {
-		app.handleErrorFormValidation(err, ErrK8sValidate, w)
-		return
-	}
-
-	// create a new agent
-	var agent *kubernetes.Agent
-
-	if app.ServerConf.IsTesting {
-		agent = app.TestAgents.K8sAgent
-	} else {
-		agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
-	}
-
-	configMap, err := agent.GetConfigMap(vals["name"][0], vals["namespace"][0])
-
-	if err != nil {
-		app.handleErrorInternal(err, w)
-		return
-	}
-
-	if err := json.NewEncoder(w).Encode(configMap); err != nil {
-		app.handleErrorFormDecoding(err, ErrEnvDecode, w)
-		return
-	}
-
-	w.WriteHeader(http.StatusOK)
-	return
-}
-
-func deleteConfigMap(agent *kubernetes.Agent, name string, namespace string) error {
-	if err := agent.DeleteLinkedSecret(name, namespace); err != nil {
-		return err
-	}
-
-	if err := agent.DeleteConfigMap(name, namespace); err != nil {
-		return err
-	}
-
-	return nil
-}
-
-// HandleDeleteConfigMap deletes the configmap (and secret) given the name and namespace.
-func (app *App) HandleDeleteConfigMap(w http.ResponseWriter, r *http.Request) {
-	vals, err := url.ParseQuery(r.URL.RawQuery)
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
-		return
-	}
-
-	// get the filter options
-	form := &forms.K8sForm{
-		OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
-			Repo:              app.Repo,
-			DigitalOceanOAuth: app.DOConf,
-		},
-	}
-
-	form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster())
-
-	// validate the form
-	if err := app.validator.Struct(form); err != nil {
-		app.handleErrorFormValidation(err, ErrK8sValidate, w)
-		return
-	}
-
-	// create a new agent
-	var agent *kubernetes.Agent
-
-	if app.ServerConf.IsTesting {
-		agent = app.TestAgents.K8sAgent
-	} else {
-		agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
-	}
-
-	if err := deleteConfigMap(agent, vals["name"][0], vals["namespace"][0]); err != nil {
-		app.handleErrorInternal(err, w)
-		return
-	}
-
-	w.WriteHeader(http.StatusOK)
-	return
-}
-
-// HandleUpdateConfigMap updates the configmap (and secret) given the name, namespace and variables.
-func (app *App) HandleUpdateConfigMap(w http.ResponseWriter, r *http.Request) {
-	vals, err := url.ParseQuery(r.URL.RawQuery)
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
-		return
-	}
-
-	// get the filter options
-	form := &forms.K8sForm{
-		OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
-			Repo:              app.Repo,
-			DigitalOceanOAuth: app.DOConf,
-		},
-	}
-
-	form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster())
-
-	// validate the form
-	if err := app.validator.Struct(form); err != nil {
-		app.handleErrorFormValidation(err, ErrK8sValidate, w)
-		return
-	}
-
-	// create a new agent
-	var agent *kubernetes.Agent
-
-	if app.ServerConf.IsTesting {
-		agent = app.TestAgents.K8sAgent
-	} else {
-		agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
-	}
-
-	configMap := &forms.ConfigMapForm{}
-
-	if err := json.NewDecoder(r.Body).Decode(configMap); err != nil {
-		app.handleErrorFormDecoding(err, ErrEnvDecode, w)
-		return
-	}
-
-	secretData := make(map[string][]byte)
-
-	for key, rawValue := range configMap.SecretEnvVariables {
-		// encodedValue, err := base64.StdEncoding.DecodeString(rawValue)
-
-		// if err != nil {
-		// 	app.handleErrorInternal(err, w)
-		// 	return
-		// }
-
-		secretData[key] = []byte(rawValue)
-	}
-
-	// create secret first
-	err = agent.UpdateLinkedSecret(configMap.Name, configMap.Namespace, configMap.Name, secretData)
-
-	if err != nil {
-		app.handleErrorInternal(err, w)
-		return
-	}
-
-	// add all secret env variables to configmap with value PORTERSECRET_${configmap_name}
-	for key, val := range configMap.SecretEnvVariables {
-		// if val is empty and key does not exist in configmap already, set to empty
-		if _, found := configMap.EnvVariables[key]; val == "" && !found {
-			configMap.EnvVariables[key] = ""
-		} else if val != "" {
-			configMap.EnvVariables[key] = fmt.Sprintf("PORTERSECRET_%s", configMap.Name)
-		}
-	}
-
-	err = agent.UpdateConfigMap(configMap.Name, configMap.Namespace, configMap.EnvVariables)
-
-	if err != nil {
-		app.handleErrorInternal(err, w)
-		return
-	}
-
-	if err := json.NewEncoder(w).Encode(configMap); err != nil {
-		app.handleErrorFormDecoding(err, ErrEnvDecode, w)
-		return
-	}
-
-	w.WriteHeader(http.StatusOK)
-	return
-}
-
-// HandleRenameConfigMap renames the configmap name given the current name, namespace and new name.
-func (app *App) HandleRenameConfigMap(w http.ResponseWriter, r *http.Request) {
-	vals, err := url.ParseQuery(r.URL.RawQuery)
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
-		return
-	}
-
-	// get the filter options
-	form := &forms.K8sForm{
-		OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
-			Repo:              app.Repo,
-			DigitalOceanOAuth: app.DOConf,
-		},
-	}
-
-	form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster())
-
-	// validate the form
-	if err := app.validator.Struct(form); err != nil {
-		app.handleErrorFormValidation(err, ErrK8sValidate, w)
-		return
-	}
-
-	// create a new agent
-	var agent *kubernetes.Agent
-
-	if app.ServerConf.IsTesting {
-		agent = app.TestAgents.K8sAgent
-	} else {
-		agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
-	}
-
-	renameConfigMapForm := &forms.RenameConfigMapForm{}
-
-	if err := json.NewDecoder(r.Body).Decode(renameConfigMapForm); err != nil {
-		app.handleErrorFormDecoding(err, ErrEnvDecode, w)
-		return
-	}
-
-	configMap, err := agent.GetConfigMap(renameConfigMapForm.Name, renameConfigMapForm.Namespace)
-
-	if err != nil {
-		app.handleErrorInternal(err, w)
-		return
-	}
-
-	secret, err := agent.GetSecret(configMap.Name, configMap.Namespace)
-
-	if err != nil {
-		app.handleErrorInternal(err, w)
-		return
-	}
-
-	var decodedSecretData = make(map[string]string)
-	for k, v := range secret.Data {
-		decodedSecretData[k] = string(v)
-	}
-
-	newConfigMapForm := &forms.ConfigMapForm{
-		Name:               renameConfigMapForm.NewName,
-		Namespace:          configMap.Namespace,
-		EnvVariables:       configMap.Data,
-		SecretEnvVariables: decodedSecretData,
-	}
-
-	if newConfigMapForm.Name == configMap.Name {
-		w.WriteHeader(http.StatusBadRequest)
-		return
-	}
-
-	newConfigMap, err := createConfigMap(agent, newConfigMapForm)
-	if err != nil {
-		app.handleErrorInternal(err, w)
-		return
-	}
-
-	if err := deleteConfigMap(agent, configMap.Name, configMap.Namespace); err != nil {
-		app.handleErrorInternal(err, w)
-		return
-	}
-
-	if err := json.NewEncoder(w).Encode(newConfigMap); err != nil {
-		app.handleErrorFormDecoding(err, ErrEnvDecode, w)
-		return
-	}
-
-	w.WriteHeader(http.StatusOK)
-	return
-}
-
-// HandleGetPodLogs returns real-time logs of the pod via websockets
-// TODO: Refactor repeated calls.
-func (app *App) HandleGetPodLogs(w http.ResponseWriter, r *http.Request) {
-	// get session to retrieve correct kubeconfig
-	_, err := app.Store.Get(r, app.ServerConf.CookieName)
-
-	// get path parameters
-	namespace := chi.URLParam(r, "namespace")
-	podName := chi.URLParam(r, "name")
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
-		return
-	}
-
-	vals, err := url.ParseQuery(r.URL.RawQuery)
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
-		return
-	}
-
-	// get the filter options
-	form := &forms.K8sForm{
-		OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
-			Repo:              app.Repo,
-			DigitalOceanOAuth: app.DOConf,
-		},
-	}
-
-	form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster())
-
-	// validate the form
-	if err := app.validator.Struct(form); err != nil {
-		app.handleErrorFormValidation(err, ErrK8sValidate, w)
-		return
-	}
-
-	// create a new agent
-	var agent *kubernetes.Agent
-
-	if app.ServerConf.IsTesting {
-		agent = app.TestAgents.K8sAgent
-	} else {
-		agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
-	}
-
-	upgrader.CheckOrigin = func(r *http.Request) bool { return true }
-
-	// upgrade to websocket.
-	conn, err := upgrader.Upgrade(w, r, nil)
-
-	if err != nil {
-		app.handleErrorUpgradeWebsocket(err, w)
-	}
-
-	err = agent.GetPodLogs(namespace, podName, conn)
-
-	if err != nil {
-		app.handleErrorWebsocketWrite(err, w)
-		return
-	}
-}
-
-// HandleDeletePod deletes the pod given the name and namespace.
-func (app *App) HandleDeletePod(w http.ResponseWriter, r *http.Request) {
-	// get path parameters
-	namespace := chi.URLParam(r, "namespace")
-	name := chi.URLParam(r, "name")
-
-	vals, err := url.ParseQuery(r.URL.RawQuery)
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
-		return
-	}
-
-	// get the filter options
-	form := &forms.K8sForm{
-		OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
-			Repo:              app.Repo,
-			DigitalOceanOAuth: app.DOConf,
-		},
-	}
-
-	form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster())
-
-	// validate the form
-	if err := app.validator.Struct(form); err != nil {
-		app.handleErrorFormValidation(err, ErrK8sValidate, w)
-		return
-	}
-
-	// create a new agent
-	var agent *kubernetes.Agent
-
-	if app.ServerConf.IsTesting {
-		agent = app.TestAgents.K8sAgent
-	} else {
-		agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
-	}
-
-	err = agent.DeletePod(namespace, name)
-
-	if err != nil {
-		app.handleErrorInternal(err, w)
-		return
-	}
-
-	w.WriteHeader(http.StatusOK)
-	return
-}
-
-// HandleGetIngress returns the ingress object given the name and namespace.
-func (app *App) HandleGetIngress(w http.ResponseWriter, r *http.Request) {
-
-	// get session to retrieve correct kubeconfig
-	_, err := app.Store.Get(r, app.ServerConf.CookieName)
-
-	// get path parameters
-	namespace := chi.URLParam(r, "namespace")
-	name := chi.URLParam(r, "name")
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
-		return
-	}
-
-	vals, err := url.ParseQuery(r.URL.RawQuery)
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
-		return
-	}
-
-	// get the filter options
-	form := &forms.K8sForm{
-		OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
-			Repo:              app.Repo,
-			DigitalOceanOAuth: app.DOConf,
-		},
-	}
-
-	form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster())
-
-	// validate the form
-	if err := app.validator.Struct(form); err != nil {
-		app.handleErrorFormValidation(err, ErrK8sValidate, w)
-		return
-	}
-
-	// create a new agent
-	var agent *kubernetes.Agent
-
-	if app.ServerConf.IsTesting {
-		agent = app.TestAgents.K8sAgent
-	} else {
-		agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
-	}
-
-	ingress, err := agent.GetIngress(namespace, name)
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
-		return
-	}
-
-	if err := json.NewEncoder(w).Encode(ingress); err != nil {
-		app.handleErrorFormDecoding(err, ErrK8sDecode, w)
-		return
-	}
-}
-
-// HandleListPods returns all pods that match the given selectors
-// TODO: Refactor repeated calls.
-func (app *App) HandleListPods(w http.ResponseWriter, r *http.Request) {
-
-	// get session to retrieve correct kubeconfig
-	_, err := app.Store.Get(r, app.ServerConf.CookieName)
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
-		return
-	}
-
-	vals, err := url.ParseQuery(r.URL.RawQuery)
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
-		return
-	}
-
-	// get the filter options
-	form := &forms.K8sForm{
-		OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
-			Repo:              app.Repo,
-			DigitalOceanOAuth: app.DOConf,
-		},
-	}
-
-	form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster())
-
-	// validate the form
-	if err := app.validator.Struct(form); err != nil {
-		app.handleErrorFormValidation(err, ErrK8sValidate, w)
-		return
-	}
-
-	// create a new agent
-	var agent *kubernetes.Agent
-
-	if app.ServerConf.IsTesting {
-		agent = app.TestAgents.K8sAgent
-	} else {
-		agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
-	}
-
-	namespace := vals.Get("namespace")
-	pods := []v1.Pod{}
-	for _, selector := range vals["selectors"] {
-		podsList, err := agent.GetPodsByLabel(selector, namespace)
-
-		if err != nil {
-			app.handleErrorFormValidation(err, ErrK8sValidate, w)
-			return
-		}
-
-		for _, pod := range podsList.Items {
-			pods = append(pods, pod)
-		}
-	}
-
-	if err := json.NewEncoder(w).Encode(pods); err != nil {
-		app.handleErrorFormDecoding(err, ErrK8sDecode, w)
-		return
-	}
-}
-
-// HandleListJobsByChart lists all jobs belonging to a specific Helm chart
-func (app *App) HandleListJobsByChart(w http.ResponseWriter, r *http.Request) {
-	// get path parameters
-	namespace := chi.URLParam(r, "namespace")
-	chart := chi.URLParam(r, "chart")
-	releaseName := chi.URLParam(r, "release_name")
-
-	vals, err := url.ParseQuery(r.URL.RawQuery)
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
-		return
-	}
-
-	// get the filter options
-	form := &forms.K8sForm{
-		OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
-			Repo:              app.Repo,
-			DigitalOceanOAuth: app.DOConf,
-		},
-	}
-
-	form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster())
-
-	// validate the form
-	if err := app.validator.Struct(form); err != nil {
-		app.handleErrorFormValidation(err, ErrK8sValidate, w)
-		return
-	}
-
-	// create a new agent
-	var agent *kubernetes.Agent
-
-	if app.ServerConf.IsTesting {
-		agent = app.TestAgents.K8sAgent
-	} else {
-		agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
-	}
-
-	jobs, err := agent.ListJobsByLabel(namespace, kubernetes.Label{
-		Key: "helm.sh/chart",
-		Val: chart,
-	}, kubernetes.Label{
-		Key: "meta.helm.sh/release-name",
-		Val: releaseName,
-	})
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
-		return
-	}
-
-	if err := json.NewEncoder(w).Encode(jobs); err != nil {
-		app.handleErrorFormDecoding(err, ErrK8sDecode, w)
-		return
-	}
-}
-
-// HandleDeleteJob deletes the job given the name and namespace.
-func (app *App) HandleDeleteJob(w http.ResponseWriter, r *http.Request) {
-	// get path parameters
-	namespace := chi.URLParam(r, "namespace")
-	name := chi.URLParam(r, "name")
-
-	vals, err := url.ParseQuery(r.URL.RawQuery)
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
-		return
-	}
-
-	// get the filter options
-	form := &forms.K8sForm{
-		OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
-			Repo:              app.Repo,
-			DigitalOceanOAuth: app.DOConf,
-		},
-	}
-
-	form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster())
-
-	// validate the form
-	if err := app.validator.Struct(form); err != nil {
-		app.handleErrorFormValidation(err, ErrK8sValidate, w)
-		return
-	}
-
-	// create a new agent
-	var agent *kubernetes.Agent
-
-	if app.ServerConf.IsTesting {
-		agent = app.TestAgents.K8sAgent
-	} else {
-		agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
-	}
-
-	err = agent.DeleteJob(name, namespace)
-
-	if err != nil {
-		app.handleErrorInternal(err, w)
-		return
-	}
-
-	w.WriteHeader(http.StatusOK)
-	return
-}
-
-// HandleStopJob stops a running job
-func (app *App) HandleStopJob(w http.ResponseWriter, r *http.Request) {
-	// get path parameters
-	namespace := chi.URLParam(r, "namespace")
-	name := chi.URLParam(r, "name")
-
-	vals, err := url.ParseQuery(r.URL.RawQuery)
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
-		return
-	}
-
-	// get the filter options
-	form := &forms.K8sForm{
-		OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
-			Repo:              app.Repo,
-			DigitalOceanOAuth: app.DOConf,
-		},
-	}
-
-	form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster())
-
-	// validate the form
-	if err := app.validator.Struct(form); err != nil {
-		app.handleErrorFormValidation(err, ErrK8sValidate, w)
-		return
-	}
-
-	// create a new agent
-	var agent *kubernetes.Agent
-
-	if app.ServerConf.IsTesting {
-		agent = app.TestAgents.K8sAgent
-	} else {
-		agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
-	}
-
-	err = agent.StopJobWithJobSidecar(namespace, name)
-
-	if err != nil {
-		app.sendExternalError(err, 500, HTTPError{
-			Code:   500,
-			Errors: []string{err.Error()},
-		}, w)
-		return
-	}
-
-	w.WriteHeader(http.StatusOK)
-	return
-}
-
-// HandleListJobPods lists all pods belonging to a specific job
-func (app *App) HandleListJobPods(w http.ResponseWriter, r *http.Request) {
-	// get path parameters
-	namespace := chi.URLParam(r, "namespace")
-	name := chi.URLParam(r, "name")
-
-	vals, err := url.ParseQuery(r.URL.RawQuery)
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
-		return
-	}
-
-	// get the filter options
-	form := &forms.K8sForm{
-		OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
-			Repo:              app.Repo,
-			DigitalOceanOAuth: app.DOConf,
-		},
-	}
-
-	form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster())
-
-	// validate the form
-	if err := app.validator.Struct(form); err != nil {
-		app.handleErrorFormValidation(err, ErrK8sValidate, w)
-		return
-	}
-
-	// create a new agent
-	var agent *kubernetes.Agent
-
-	if app.ServerConf.IsTesting {
-		agent = app.TestAgents.K8sAgent
-	} else {
-		agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
-	}
-
-	pods, err := agent.GetJobPods(namespace, name)
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
-		return
-	}
-
-	if err := json.NewEncoder(w).Encode(pods); err != nil {
-		app.handleErrorFormDecoding(err, ErrK8sDecode, w)
-		return
-	}
-}
-
-// HandleStreamControllerStatus test calls
-// TODO: Refactor repeated calls.
-func (app *App) HandleStreamControllerStatus(w http.ResponseWriter, r *http.Request) {
-	vals, err := url.ParseQuery(r.URL.RawQuery)
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
-		return
-	}
-
-	// get session to retrieve correct kubeconfig
-	_, err = app.Store.Get(r, app.ServerConf.CookieName)
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
-		return
-	}
-
-	// get the filter options
-	form := &forms.K8sForm{
-		OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
-			Repo:              app.Repo,
-			DigitalOceanOAuth: app.DOConf,
-		},
-	}
-
-	form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster())
-
-	// validate the form
-	if err := app.validator.Struct(form); err != nil {
-		app.handleErrorFormValidation(err, ErrK8sValidate, w)
-		return
-	}
-
-	// create a new agent
-	var agent *kubernetes.Agent
-
-	if app.ServerConf.IsTesting {
-		agent = app.TestAgents.K8sAgent
-	} else {
-		agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
-	}
-
-	upgrader.CheckOrigin = func(r *http.Request) bool { return true }
-
-	// upgrade to websocket.
-	conn, err := upgrader.Upgrade(w, r, nil)
-
-	if err != nil {
-		app.handleErrorUpgradeWebsocket(err, w)
-	}
-
-	// get path parameters
-	kind := chi.URLParam(r, "kind")
-
-	selectors := ""
-	if vals["selectors"] != nil {
-		selectors = vals["selectors"][0]
-	}
-	err = agent.StreamControllerStatus(conn, kind, selectors)
-
-	if err != nil {
-		app.handleErrorWebsocketWrite(err, w)
-		return
-	}
-}
-
-func (app *App) HandleStreamHelmReleases(w http.ResponseWriter, r *http.Request) {
-	vals, err := url.ParseQuery(r.URL.RawQuery)
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
-		return
-	}
-
-	// get session to retrieve correct kubeconfig
-	_, err = app.Store.Get(r, app.ServerConf.CookieName)
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
-		return
-	}
-
-	// get the filter options
-	form := &forms.K8sForm{
-		OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
-			Repo:              app.Repo,
-			DigitalOceanOAuth: app.DOConf,
-		},
-	}
-
-	form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster())
-
-	// validate the form
-	if err := app.validator.Struct(form); err != nil {
-		app.handleErrorFormValidation(err, ErrK8sValidate, w)
-		return
-	}
-
-	// create a new agent
-	var agent *kubernetes.Agent
-
-	if app.ServerConf.IsTesting {
-		agent = app.TestAgents.K8sAgent
-	} else {
-		agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
-	}
-
-	upgrader.CheckOrigin = func(r *http.Request) bool { return true }
-
-	// upgrade to websocket.
-	conn, err := upgrader.Upgrade(w, r, nil)
-
-	if err != nil {
-		app.handleErrorUpgradeWebsocket(err, w)
-	}
-
-	selectors := ""
-	if vals["selectors"] != nil {
-		selectors = vals["selectors"][0]
-	}
-
-	var chartList []string
-
-	if vals["charts"] != nil {
-		chartList = vals["charts"]
-	}
-
-	namespace := v1.NamespaceAll
-	if vals["namespace"] != nil {
-		namespace = vals["namespace"][0]
-	}
-
-	err = agent.StreamHelmReleases(conn, namespace, chartList, selectors)
-
-	if err != nil {
-		app.handleErrorWebsocketWrite(err, w)
-		return
-	}
-}
-
-// HandleDetectPrometheusInstalled detects a prometheus installation in the target cluster
-func (app *App) HandleDetectPrometheusInstalled(w http.ResponseWriter, r *http.Request) {
-	vals, err := url.ParseQuery(r.URL.RawQuery)
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
-		return
-	}
-
-	// get the filter options
-	form := &forms.K8sForm{
-		OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
-			Repo:              app.Repo,
-			DigitalOceanOAuth: app.DOConf,
-		},
-	}
-
-	form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster())
-
-	// validate the form
-	if err := app.validator.Struct(form); err != nil {
-		app.handleErrorFormValidation(err, ErrK8sValidate, w)
-		return
-	}
-
-	// create a new agent
-	var agent *kubernetes.Agent
-
-	if app.ServerConf.IsTesting {
-		agent = app.TestAgents.K8sAgent
-	} else {
-		agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
-	}
-
-	// detect prometheus service
-	_, found, err := prometheus.GetPrometheusService(agent.Clientset)
-
-	if !found {
-		http.NotFound(w, r)
-		return
-	}
-
-	w.WriteHeader(http.StatusOK)
-	return
-}
-
-// HandleListNGINXIngresses lists all NGINX ingresses in a target cluster
-func (app *App) HandleListNGINXIngresses(w http.ResponseWriter, r *http.Request) {
-	vals, err := url.ParseQuery(r.URL.RawQuery)
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
-		return
-	}
-
-	// get the filter options
-	form := &forms.K8sForm{
-		OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
-			Repo:              app.Repo,
-			DigitalOceanOAuth: app.DOConf,
-		},
-	}
-
-	form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster())
-
-	// validate the form
-	if err := app.validator.Struct(form); err != nil {
-		app.handleErrorFormValidation(err, ErrK8sValidate, w)
-		return
-	}
-
-	// create a new agent
-	var agent *kubernetes.Agent
-
-	if app.ServerConf.IsTesting {
-		agent = app.TestAgents.K8sAgent
-	} else {
-		agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
-	}
-
-	res, err := prometheus.GetIngressesWithNGINXAnnotation(agent.Clientset)
-
-	if err != nil {
-		app.handleErrorInternal(err, w)
-		return
-	}
-
-	w.WriteHeader(http.StatusOK)
-
-	if err := json.NewEncoder(w).Encode(res); err != nil {
-		app.handleErrorFormDecoding(err, ErrK8sDecode, w)
-		return
-	}
-}
-
-func (app *App) HandleGetPodMetrics(w http.ResponseWriter, r *http.Request) {
-	vals, err := url.ParseQuery(r.URL.RawQuery)
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
-		return
-	}
-
-	// get the filter options
-	form := &forms.MetricsQueryForm{
-		K8sForm: &forms.K8sForm{
-			OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
-				Repo:              app.Repo,
-				DigitalOceanOAuth: app.DOConf,
-			},
-		},
-		QueryOpts: &prometheus.QueryOpts{},
-	}
-
-	form.K8sForm.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster())
-
-	// decode from JSON to form value
-	decoder := schema.NewDecoder()
-	decoder.IgnoreUnknownKeys(true)
-
-	if err := decoder.Decode(form.QueryOpts, vals); err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	// validate the form
-	if err := app.validator.Struct(form); err != nil {
-		app.handleErrorFormValidation(err, ErrK8sValidate, w)
-		return
-	}
-
-	// create a new agent
-	var agent *kubernetes.Agent
-
-	if app.ServerConf.IsTesting {
-		agent = app.TestAgents.K8sAgent
-	} else {
-		agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
-	}
-
-	// get prometheus service
-	promSvc, found, err := prometheus.GetPrometheusService(agent.Clientset)
-
-	if err != nil {
-		app.handleErrorInternal(err, w)
-		return
-	}
-
-	if !found {
-		app.handleErrorInternal(err, w)
-		return
-	}
-
-	rawQuery, err := prometheus.QueryPrometheus(agent.Clientset, promSvc, form.QueryOpts)
-
-	if err != nil {
-		app.handleErrorInternal(err, w)
-		return
-	}
-
-	fmt.Fprint(w, string(rawQuery))
-}
-
-type KubeconfigResponse struct {
-	Kubeconfig []byte `json:"kubeconfig"`
-}
-
-func (app *App) HandleGetTemporaryKubeconfig(w http.ResponseWriter, r *http.Request) {
-	vals, err := url.ParseQuery(r.URL.RawQuery)
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
-		return
-	}
-
-	// get the filter options
-	form := &forms.K8sForm{
-		OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
-			Repo:              app.Repo,
-			DigitalOceanOAuth: app.DOConf,
-		},
-	}
-
-	form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster())
-
-	// get the API config
-	apiConf, err := form.OutOfClusterConfig.CreateRawConfigFromCluster()
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	bytes, err := clientcmd.Write(*apiConf)
-	res := &KubeconfigResponse{
-		Kubeconfig: bytes,
-	}
-
-	if err := json.NewEncoder(w).Encode(res); err != nil {
-		app.handleErrorFormDecoding(err, ErrK8sDecode, w)
-		return
-	}
-}
-
-func (app *App) HandleListNodes(w http.ResponseWriter, r *http.Request) {
-	id, err := strconv.ParseUint(chi.URLParam(r, "cluster_id"), 0, 64)
-
-	if err != nil || id == 0 {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	cluster, err := app.Repo.Cluster().ReadCluster(uint(id))
-
-	if err != nil {
-		app.handleErrorRead(err, ErrProjectDataRead, w)
-		return
-	}
-
-	form := &forms.K8sForm{
-		OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
-			Repo:              app.Repo,
-			DigitalOceanOAuth: app.DOConf,
-			Cluster:           cluster,
-		},
-	}
-
-	var agent *kubernetes.Agent
-
-	if app.ServerConf.IsTesting {
-		agent = app.TestAgents.K8sAgent
-	} else {
-		agent, _ = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
-	}
-
-	nodeWithUsageList := nodes.GetNodesUsage(agent.Clientset)
-
-	w.WriteHeader(http.StatusOK)
-
-	if err := json.NewEncoder(w).Encode(nodeWithUsageList); err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-}
-
-func (app *App) HandleGetNode(w http.ResponseWriter, r *http.Request) {
-	cluster_id, err := strconv.ParseUint(chi.URLParam(r, "cluster_id"), 0, 64)
-	node_name := chi.URLParam(r, "node_name")
-
-	if err != nil || cluster_id == 0 {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	cluster, err := app.Repo.Cluster().ReadCluster(uint(cluster_id))
-
-	if err != nil {
-		app.handleErrorRead(err, ErrProjectDataRead, w)
-		return
-	}
-
-	form := &forms.K8sForm{
-		OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
-			Repo:              app.Repo,
-			DigitalOceanOAuth: app.DOConf,
-			Cluster:           cluster,
-		},
-	}
-
-	var agent *kubernetes.Agent
-
-	if app.ServerConf.IsTesting {
-		agent = app.TestAgents.K8sAgent
-	} else {
-		agent, _ = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
-	}
-
-	nodeWithUsageData := nodes.DescribeNode(agent.Clientset, node_name)
-
-	w.WriteHeader(http.StatusOK)
-
-	if err := json.NewEncoder(w).Encode(nodeWithUsageData); err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-}

+ 0 - 128
server/api/k8s_handler_test.go

@@ -1,128 +0,0 @@
-package api_test
-
-// // ------------------------- TEST TYPES AND MAIN LOOP ------------------------- //
-
-// type k8sTest struct {
-// 	initializers []func(tester *tester)
-// 	msg          string
-// 	method       string
-// 	endpoint     string
-// 	body         string
-// 	expStatus    int
-// 	expBody      string
-// 	useCookie    bool
-// 	validators   []func(c *k8sTest, tester *tester, t *testing.T)
-// }
-
-// func testK8sRequests(t *testing.T, tests []*k8sTest, canQuery bool) {
-// 	for _, c := range tests {
-// 		// create a new tester
-// 		tester := newTester(canQuery)
-
-// 		// if there's an initializer, call it
-// 		for _, init := range c.initializers {
-// 			init(tester)
-// 		}
-
-// 		req, err := http.NewRequest(
-// 			c.method,
-// 			c.endpoint,
-// 			strings.NewReader(c.body),
-// 		)
-
-// 		tester.req = req
-
-// 		if c.useCookie {
-// 			req.AddCookie(tester.cookie)
-// 		}
-
-// 		if err != nil {
-// 			t.Fatal(err)
-// 		}
-
-// 		tester.execute()
-// 		rr := tester.rr
-
-// 		// first, check that the status matches
-// 		if status := rr.Code; status != c.expStatus {
-// 			t.Errorf("%s, handler returned wrong status code: got %v want %v",
-// 				c.msg, status, c.expStatus)
-// 		}
-
-// 		// if there's a validator, call it
-// 		for _, validate := range c.validators {
-// 			validate(c, tester, t)
-// 		}
-// 	}
-// }
-
-// // ------------------------- TEST FIXTURES AND FUNCTIONS  ------------------------- //
-
-// var listNamespacesTests = []*k8sTest{
-// 	{
-// 		initializers: []func(tester *tester){
-// 			initDefaultK8s,
-// 		},
-// 		msg:    "List namespaces",
-// 		method: "GET",
-// 		endpoint: "/api/projects/1/k8s/namespaces?" + url.Values{
-// 			"cluster_id": []string{"1"},
-// 		}.Encode(),
-// 		body:      "",
-// 		expStatus: http.StatusOK,
-// 		expBody:   objectsToJSON(defaultObjects),
-// 		useCookie: true,
-// 		validators: []func(c *k8sTest, tester *tester, t *testing.T){
-// 			k8sNamespaceListValidator,
-// 		},
-// 	},
-// }
-
-// func TestHandleListNamespaces(t *testing.T) {
-// 	testK8sRequests(t, listNamespacesTests, true)
-// }
-
-// // ------------------------- INITIALIZERS AND VALIDATORS ------------------------- //
-
-// var defaultObjects = []runtime.Object{
-// 	&v1.Namespace{
-// 		ObjectMeta: metav1.ObjectMeta{
-// 			Name: "test-namespace-0",
-// 		},
-// 	},
-// 	&v1.Namespace{
-// 		ObjectMeta: metav1.ObjectMeta{
-// 			Name: "test-namespace-1",
-// 		},
-// 	},
-// }
-
-// func initDefaultK8s(tester *tester) {
-// 	initUserDefault(tester)
-// 	initProject(tester)
-// 	initProjectClusterDefault(tester)
-
-// 	agent := kubernetes.GetAgentTesting(defaultObjects...)
-
-// 	// overwrite the test agent with new resources
-// 	tester.app.TestAgents.K8sAgent = agent
-// }
-
-// func objectsToJSON(objs []runtime.Object) string {
-// 	str, _ := json.Marshal(objs)
-
-// 	return string(str)
-// }
-
-// func k8sNamespaceListValidator(c *k8sTest, tester *tester, t *testing.T) {
-// 	gotBody := &v1.NamespaceList{}
-// 	expBody := &[]v1.Namespace{}
-
-// 	json.Unmarshal(tester.rr.Body.Bytes(), gotBody)
-// 	json.Unmarshal([]byte(c.expBody), expBody)
-
-// 	if !reflect.DeepEqual(gotBody.Items, *expBody) {
-// 		t.Errorf("%s, handler returned wrong body: got %v want %v",
-// 			c.msg, gotBody.Items, expBody)
-// 	}
-// }

+ 0 - 140
server/api/notifications_handler.go

@@ -1,140 +0,0 @@
-package api
-
-import (
-	"encoding/json"
-	"net/http"
-	"net/url"
-	"strconv"
-
-	"github.com/go-chi/chi"
-	"github.com/porter-dev/porter/internal/models"
-	"gorm.io/gorm"
-)
-
-type HandleUpdateNotificationConfigForm struct {
-	Payload struct {
-		Enabled bool `json:"enabled"`
-		Success bool `json:"success"`
-		Failure bool `json:"failure"`
-	} `json:"payload"`
-	Namespace string `json:"namespace"`
-	ClusterID uint   `json:"cluster_id"`
-}
-
-// HandleUpdateNotificationConfig updates notification settings for a given release
-func (app *App) HandleUpdateNotificationConfig(w http.ResponseWriter, r *http.Request) {
-	name := chi.URLParam(r, "name")
-
-	form := &HandleUpdateNotificationConfigForm{}
-
-	if err := json.NewDecoder(r.Body).Decode(form); err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	release, err := app.Repo.Release().ReadRelease(form.ClusterID, name, form.Namespace)
-
-	if err != nil {
-		if err == gorm.ErrRecordNotFound {
-			w.WriteHeader(http.StatusNotFound)
-			return
-		}
-
-		app.sendExternalError(err, http.StatusInternalServerError, HTTPError{
-			Code:   ErrReleaseReadData,
-			Errors: []string{"release not found"},
-		}, w)
-	}
-
-	// either create a new notification config or update the current one
-	newConfig := &models.NotificationConfig{
-		Enabled: form.Payload.Enabled,
-		Success: form.Payload.Success,
-		Failure: form.Payload.Failure,
-	}
-
-	if release.NotificationConfig == 0 {
-		newConfig, err = app.Repo.NotificationConfig().CreateNotificationConfig(newConfig)
-
-		if err != nil {
-			app.handleErrorInternal(err, w)
-			return
-		}
-
-		release.NotificationConfig = newConfig.ID
-
-		release, err = app.Repo.Release().UpdateRelease(release)
-
-	} else {
-		newConfig.ID = release.NotificationConfig
-		newConfig, err = app.Repo.NotificationConfig().UpdateNotificationConfig(newConfig)
-	}
-
-	if err != nil {
-		app.handleErrorInternal(err, w)
-		return
-	}
-
-	w.WriteHeader(http.StatusOK)
-}
-
-// HandleGetNotificationConfig gets the notification config for a given release
-func (app *App) HandleGetNotificationConfig(w http.ResponseWriter, r *http.Request) {
-	projID, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
-
-	if err != nil || projID == 0 {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	vals, err := url.ParseQuery(r.URL.RawQuery)
-	name := chi.URLParam(r, "name")
-	namespace := vals["namespace"][0]
-
-	clusterID, err := strconv.ParseUint(vals["cluster_id"][0], 10, 64)
-
-	if err != nil {
-		app.sendExternalError(err, http.StatusInternalServerError, HTTPError{
-			Code:   ErrReleaseReadData,
-			Errors: []string{"release not found"},
-		}, w)
-		return
-	}
-
-	release, err := app.Repo.Release().ReadRelease(uint(clusterID), name, namespace)
-
-	if err != nil {
-		if err == gorm.ErrRecordNotFound {
-			w.WriteHeader(http.StatusNotFound)
-			return
-		}
-
-		app.sendExternalError(err, http.StatusInternalServerError, HTTPError{
-			Code:   ErrReleaseReadData,
-			Errors: []string{"release not found"},
-		}, w)
-		return
-	}
-
-	config := &models.NotificationConfigExternal{
-		Enabled: true,
-		Success: true,
-		Failure: true,
-	}
-
-	if release.NotificationConfig != 0 {
-		notifConfig, err := app.Repo.NotificationConfig().ReadNotificationConfig(release.NotificationConfig)
-
-		if err != nil {
-			app.handleErrorInternal(err, w)
-		}
-
-		config = notifConfig.Externalize()
-	}
-
-	err = json.NewEncoder(w).Encode(config)
-
-	if err != nil {
-		app.handleErrorInternal(err, w)
-	}
-}

+ 0 - 102
server/api/oauth_do_handler.go

@@ -1,102 +0,0 @@
-package api
-
-import (
-	"fmt"
-	"net/http"
-
-	"github.com/porter-dev/porter/internal/oauth"
-	"golang.org/x/oauth2"
-
-	"github.com/porter-dev/porter/internal/models/integrations"
-)
-
-// HandleDOOAuthStartProject starts the oauth2 flow for a project digitalocean request.
-// In this handler, the project id gets written to the session (along with the oauth
-// state param), so that the correct project id can be identified in the callback.
-func (app *App) HandleDOOAuthStartProject(w http.ResponseWriter, r *http.Request) {
-	state := oauth.CreateRandomState()
-
-	err := app.populateOAuthSession(w, r, state, true)
-
-	if err != nil {
-		app.handleErrorDataRead(err, w)
-		return
-	}
-
-	// specify access type offline to get a refresh token
-	url := app.DOConf.AuthCodeURL(state, oauth2.AccessTypeOffline)
-
-	http.Redirect(w, r, url, 302)
-}
-
-// HandleDOOAuthCallback verifies the callback request by checking that the
-// state parameter has not been modified, and validates the token.
-func (app *App) HandleDOOAuthCallback(w http.ResponseWriter, r *http.Request) {
-	session, err := app.Store.Get(r, app.ServerConf.CookieName)
-
-	if err != nil {
-		app.handleErrorDataRead(err, w)
-		return
-	}
-
-	if _, ok := session.Values["state"]; !ok {
-		app.sendExternalError(
-			err,
-			http.StatusForbidden,
-			HTTPError{
-				Code: http.StatusForbidden,
-				Errors: []string{
-					"Could not read cookie: are cookies enabled?",
-				},
-			},
-			w,
-		)
-
-		return
-	}
-
-	if r.URL.Query().Get("state") != session.Values["state"] {
-		http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
-		return
-	}
-
-	token, err := app.DOConf.Exchange(oauth2.NoContext, r.URL.Query().Get("code"))
-
-	if err != nil {
-		http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
-		return
-	}
-
-	if !token.Valid() {
-		http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
-		return
-	}
-
-	userID, _ := session.Values["user_id"].(uint)
-	projID, _ := session.Values["project_id"].(uint)
-
-	oauthInt := &integrations.OAuthIntegration{
-		SharedOAuthModel: integrations.SharedOAuthModel{
-			AccessToken:  []byte(token.AccessToken),
-			RefreshToken: []byte(token.RefreshToken),
-			Expiry:       token.Expiry,
-		},
-		Client:    integrations.OAuthDigitalOcean,
-		UserID:    userID,
-		ProjectID: projID,
-	}
-
-	// create the oauth integration first
-	oauthInt, err = app.Repo.OAuthIntegration().CreateOAuthIntegration(oauthInt)
-
-	if err != nil {
-		app.handleErrorDataWrite(err, w)
-		return
-	}
-
-	if session.Values["query_params"] != "" {
-		http.Redirect(w, r, fmt.Sprintf("/dashboard?%s", session.Values["query_params"]), 302)
-	} else {
-		http.Redirect(w, r, "/dashboard", 302)
-	}
-}

+ 0 - 370
server/api/oauth_github_handler.go

@@ -1,370 +0,0 @@
-package api
-
-import (
-	"context"
-	"fmt"
-	"net/http"
-	"net/url"
-	"strconv"
-	"strings"
-
-	"github.com/porter-dev/porter/internal/analytics"
-	"github.com/porter-dev/porter/internal/models"
-	"gorm.io/gorm"
-
-	"github.com/go-chi/chi"
-	"github.com/google/go-github/github"
-	"github.com/porter-dev/porter/internal/oauth"
-	"golang.org/x/oauth2"
-
-	"github.com/porter-dev/porter/internal/models/integrations"
-)
-
-// HandleGithubOAuthStartUser starts the oauth2 flow for a user login request.
-func (app *App) HandleGithubOAuthStartUser(w http.ResponseWriter, r *http.Request) {
-	state := oauth.CreateRandomState()
-
-	err := app.populateOAuthSession(w, r, state, false)
-
-	if err != nil {
-		app.handleErrorDataRead(err, w)
-		return
-	}
-
-	// specify access type offline to get a refresh token
-	url := app.GithubUserConf.AuthCodeURL(state, oauth2.AccessTypeOnline)
-
-	http.Redirect(w, r, url, 302)
-}
-
-// HandleGithubOAuthStartProject starts the oauth2 flow for a project repo request.
-// In this handler, the project id gets written to the session (along with the oauth
-// state param), so that the correct project id can be identified in the callback.
-func (app *App) HandleGithubOAuthStartProject(w http.ResponseWriter, r *http.Request) {
-	state := oauth.CreateRandomState()
-
-	err := app.populateOAuthSession(w, r, state, true)
-
-	if err != nil {
-		app.handleErrorDataRead(err, w)
-		return
-	}
-
-	// specify access type offline to get a refresh token
-	url := app.GithubProjectConf.AuthCodeURL(state, oauth2.AccessTypeOffline)
-
-	http.Redirect(w, r, url, 302)
-}
-
-// HandleGithubOAuthCallback verifies the callback request by checking that the
-// state parameter has not been modified, and validates the token.
-// There is a difference between the oauth flow when logging a user in, and when
-// linking a repository.
-//
-// When logging a user in, the access token gets stored in the session, and no refresh
-// token is requested. We store the access token in the session because a user can be
-// logged in multiple times with a single access token.
-//
-// NOTE: this user flow will likely be augmented with Dex, or entirely replaced with Dex.
-//
-// However, when linking a repository, the access token and refresh token are requested when
-// the flow has started. A project also gets linked to the session. After callback, a new
-// github config gets stored for the project, and the user will then get redirected to
-// a URL that allows them to select their repositories they'd like to link. We require a refresh
-// token because we need permanent access to the linked repository.
-func (app *App) HandleGithubOAuthCallback(w http.ResponseWriter, r *http.Request) {
-	session, err := app.Store.Get(r, app.ServerConf.CookieName)
-
-	if err != nil {
-		app.handleErrorDataRead(err, w)
-		return
-	}
-
-	if _, ok := session.Values["state"]; !ok {
-		app.sendExternalError(
-			err,
-			http.StatusForbidden,
-			HTTPError{
-				Code: http.StatusForbidden,
-				Errors: []string{
-					"Could not read cookie: are cookies enabled?",
-				},
-			},
-			w,
-		)
-
-		return
-	}
-
-	if r.URL.Query().Get("state") != session.Values["state"] {
-		http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
-		return
-	}
-
-	token, err := app.GithubProjectConf.Exchange(oauth2.NoContext, r.URL.Query().Get("code"))
-
-	if err != nil {
-		http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
-		return
-	}
-
-	if !token.Valid() {
-		http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
-		return
-	}
-
-	if session.Values["project_id"] != nil && session.Values["project_id"] != "" {
-		userID, _ := session.Values["user_id"].(uint)
-		projID, _ := session.Values["project_id"].(uint)
-
-		app.updateProjectFromToken(projID, userID, token)
-	} else {
-		// otherwise, create the user if not exists
-		user, err := app.upsertUserFromToken(token)
-
-		if err != nil && strings.Contains(err.Error(), "already registered") {
-			http.Redirect(w, r, "/login?error="+url.QueryEscape(err.Error()), 302)
-			return
-		} else if err != nil {
-			http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
-			return
-		}
-
-		// send to segment
-		app.AnalyticsClient.Identify(analytics.CreateSegmentIdentifyUser(user))
-
-		// log the user in
-		app.Logger.Info().Msgf("New user created: %d", user.ID)
-
-		session.Values["authenticated"] = true
-		session.Values["user_id"] = user.ID
-		session.Values["email"] = user.Email
-		session.Values["redirect"] = ""
-		session.Save(r, w)
-	}
-
-	if session.Values["query_params"] != "" {
-		http.Redirect(w, r, fmt.Sprintf("/dashboard?%s", session.Values["query_params"]), 302)
-	} else {
-		http.Redirect(w, r, "/dashboard", 302)
-	}
-}
-
-func (app *App) populateOAuthSession(w http.ResponseWriter, r *http.Request, state string, isProject bool) error {
-	session, err := app.Store.Get(r, app.ServerConf.CookieName)
-
-	if err != nil {
-		return err
-	}
-
-	// need state parameter to validate when redirected
-	session.Values["state"] = state
-	session.Values["query_params"] = r.URL.RawQuery
-
-	if isProject {
-		// read the project id and add it to the session
-		projID, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
-
-		if err != nil || projID == 0 {
-			return fmt.Errorf("could not read project id")
-		}
-
-		session.Values["project_id"] = uint(projID)
-	}
-
-	if err := session.Save(r, w); err != nil {
-		app.Logger.Warn().Err(err)
-	}
-
-	return nil
-}
-
-func (app *App) upsertUserFromToken(tok *oauth2.Token) (*models.User, error) {
-	// determine if the user already exists
-	client := github.NewClient(app.GithubProjectConf.Client(oauth2.NoContext, tok))
-
-	githubUser, _, err := client.Users.Get(context.Background(), "")
-
-	if err != nil {
-		return nil, err
-	}
-
-	user, err := app.Repo.User().ReadUserByGithubUserID(*githubUser.ID)
-
-	// if the user does not exist, create new user
-	if err != nil && err == gorm.ErrRecordNotFound {
-		emails, _, err := client.Users.ListEmails(context.Background(), &github.ListOptions{})
-
-		if err != nil {
-			return nil, err
-		}
-
-		primary := ""
-		verified := false
-
-		// get the primary email
-		for _, email := range emails {
-			if email.GetPrimary() {
-				primary = email.GetEmail()
-				verified = email.GetVerified()
-				break
-			}
-		}
-
-		if primary == "" {
-			return nil, fmt.Errorf("github user must have an email")
-		}
-
-		// check if a user with that email address already exists
-		_, err = app.Repo.User().ReadUserByEmail(primary)
-
-		if err == gorm.ErrRecordNotFound {
-			user = &models.User{
-				Email:         primary,
-				EmailVerified: !app.Capabilities.Email || verified,
-				GithubUserID:  githubUser.GetID(),
-			}
-
-			user, err = app.Repo.User().CreateUser(user)
-
-			if err != nil {
-				return nil, err
-			}
-
-			app.AnalyticsClient.Track(analytics.UserCreateTrack(&analytics.UserCreateTrackOpts{
-				UserScopedTrackOpts: analytics.GetUserScopedTrackOpts(user.ID),
-				Email:               user.Email,
-			}))
-
-			if !verified {
-				// non-fatal email verification flow
-				app.startEmailVerificationFlow(user)
-			}
-		} else if err == nil {
-			return nil, fmt.Errorf("email already registered")
-		} else if err != nil {
-			return nil, err
-		}
-	} else if err != nil {
-		return nil, fmt.Errorf("unexpected error occurred:%s", err.Error())
-	}
-
-	return user, nil
-}
-
-// updates a project's repository clients with the token information.
-func (app *App) updateProjectFromToken(projectID uint, userID uint, tok *oauth2.Token) error {
-	// get the list of repositories that this token has access to
-	client := github.NewClient(app.GithubProjectConf.Client(oauth2.NoContext, tok))
-
-	user, _, err := client.Users.Get(context.Background(), "")
-
-	if err != nil {
-		return err
-	}
-
-	oauthInt := &integrations.OAuthIntegration{
-		SharedOAuthModel: integrations.SharedOAuthModel{
-			AccessToken:  []byte(tok.AccessToken),
-			RefreshToken: []byte(tok.RefreshToken),
-		},
-		Client:    integrations.OAuthGithub,
-		UserID:    userID,
-		ProjectID: projectID,
-	}
-
-	// create the oauth integration first
-	oauthInt, err = app.Repo.OAuthIntegration().CreateOAuthIntegration(oauthInt)
-
-	if err != nil {
-		return err
-	}
-
-	// create the git repo
-	gr := &models.GitRepo{
-		ProjectID:          projectID,
-		RepoEntity:         *user.Login,
-		OAuthIntegrationID: oauthInt.ID,
-	}
-
-	gr, err = app.Repo.GitRepo().CreateGitRepo(gr)
-
-	return err
-}
-
-// HandleGithubAppOAuthCallback handles the oauth callback from the GitHub app oauth flow
-// this basically just involves generating an access token and then linking it to the current user
-func (app *App) HandleGithubAppOAuthCallback(w http.ResponseWriter, r *http.Request) {
-	session, err := app.Store.Get(r, app.ServerConf.CookieName)
-
-	if err != nil {
-		app.handleErrorDataRead(err, w)
-		return
-	}
-
-	token, err := app.GithubAppConf.Exchange(oauth2.NoContext, r.URL.Query().Get("code"))
-
-	if err != nil || !token.Valid() {
-		if session.Values["query_params"] != "" {
-			http.Redirect(w, r, fmt.Sprintf("/dashboard?%s", session.Values["query_params"]), 302)
-		} else {
-			http.Redirect(w, r, "/dashboard", 302)
-		}
-		return
-	}
-
-	fmt.Println("exchange happaned")
-	fmt.Println(token.AccessToken)
-	fmt.Println(token.RefreshToken)
-
-	userID, err := app.getUserIDFromRequest(r)
-
-	if err != nil {
-		app.handleErrorInternal(err, w)
-		return
-	}
-
-	user, err := app.Repo.User().ReadUser(userID)
-
-	if err != nil {
-		app.handleErrorInternal(err, w)
-		return
-	}
-
-	oauthInt := &integrations.GithubAppOAuthIntegration{
-		SharedOAuthModel: integrations.SharedOAuthModel{
-			AccessToken:  []byte(token.AccessToken),
-			RefreshToken: []byte(token.RefreshToken),
-			Expiry:       token.Expiry,
-		},
-		UserID: user.ID,
-	}
-
-	oauthInt, err = app.Repo.GithubAppOAuthIntegration().CreateGithubAppOAuthIntegration(oauthInt)
-
-	if err != nil {
-		app.handleErrorInternal(err, w)
-		return
-	}
-
-	user.GithubAppIntegrationID = oauthInt.ID
-
-	user, err = app.Repo.User().UpdateUser(user)
-
-	if err != nil {
-		app.handleErrorInternal(err, w)
-		return
-	}
-
-	app.AnalyticsClient.Track(analytics.GithubConnectionSuccessTrack(
-		&analytics.GithubConnectionSuccessTrackOpts{
-			UserScopedTrackOpts: analytics.GetUserScopedTrackOpts(user.ID),
-		},
-	))
-
-	if session.Values["query_params"] != "" {
-		http.Redirect(w, r, fmt.Sprintf("/dashboard?%s", session.Values["query_params"]), 302)
-	} else {
-		http.Redirect(w, r, "/dashboard", 302)
-	}
-}

+ 0 - 202
server/api/oauth_google_handler.go

@@ -1,202 +0,0 @@
-package api
-
-import (
-	"encoding/json"
-	"fmt"
-	"io/ioutil"
-	"net/http"
-	"net/url"
-	"strings"
-
-	"github.com/porter-dev/porter/internal/analytics"
-	"github.com/porter-dev/porter/internal/models"
-	"gorm.io/gorm"
-
-	"github.com/porter-dev/porter/internal/oauth"
-	"golang.org/x/oauth2"
-)
-
-// HandleGoogleStartUser starts the oauth2 flow for a user login request.
-func (app *App) HandleGoogleStartUser(w http.ResponseWriter, r *http.Request) {
-	state := oauth.CreateRandomState()
-
-	err := app.populateOAuthSession(w, r, state, false)
-
-	if err != nil {
-		app.handleErrorDataRead(err, w)
-		return
-	}
-
-	// specify access type offline to get a refresh token
-	url := app.GoogleUserConf.AuthCodeURL(state, oauth2.AccessTypeOnline)
-
-	http.Redirect(w, r, url, 302)
-}
-
-// HandleGithubOAuthCallback verifies the callback request by checking that the
-// state parameter has not been modified, and validates the token.
-//
-// When logging a user in, the access token gets stored in the session, and no refresh
-// token is requested. We store the access token in the session because a user can be
-// logged in multiple times with a single access token.
-func (app *App) HandleGoogleOAuthCallback(w http.ResponseWriter, r *http.Request) {
-	session, err := app.Store.Get(r, app.ServerConf.CookieName)
-
-	if err != nil {
-		app.handleErrorDataRead(err, w)
-		return
-	}
-
-	if _, ok := session.Values["state"]; !ok {
-		app.sendExternalError(
-			err,
-			http.StatusForbidden,
-			HTTPError{
-				Code: http.StatusForbidden,
-				Errors: []string{
-					"Could not read cookie: are cookies enabled?",
-				},
-			},
-			w,
-		)
-
-		return
-	}
-
-	if r.URL.Query().Get("state") != session.Values["state"] {
-		http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
-		return
-	}
-
-	token, err := app.GoogleUserConf.Exchange(oauth2.NoContext, r.URL.Query().Get("code"))
-
-	if err != nil {
-		http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
-		return
-	}
-
-	if !token.Valid() {
-		http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
-		return
-	}
-
-	// create the user if not exists
-	user, err := app.upsertGoogleUserFromToken(token)
-
-	if err != nil && strings.Contains(err.Error(), "already registered") {
-		http.Redirect(w, r, "/login?error="+url.QueryEscape(err.Error()), 302)
-		return
-	} else if err != nil && strings.Contains(err.Error(), "restricted domain group") {
-		http.Redirect(w, r, "/login?error="+url.QueryEscape(err.Error()), 302)
-		return
-	} else if err != nil {
-		http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
-		return
-	}
-
-	// send to segment
-	app.AnalyticsClient.Identify(analytics.CreateSegmentIdentifyUser(user))
-
-	app.AnalyticsClient.Track(analytics.UserCreateTrack(&analytics.UserCreateTrackOpts{
-		UserScopedTrackOpts: analytics.GetUserScopedTrackOpts(user.ID),
-		Email:               user.Email,
-	}))
-
-	// log the user in
-	app.Logger.Info().Msgf("New user created: %d", user.ID)
-
-	session.Values["authenticated"] = true
-	session.Values["user_id"] = user.ID
-	session.Values["email"] = user.Email
-	session.Values["redirect"] = ""
-	session.Save(r, w)
-
-	if session.Values["query_params"] != "" {
-		http.Redirect(w, r, fmt.Sprintf("/dashboard?%s", session.Values["query_params"]), 302)
-	} else {
-		http.Redirect(w, r, "/dashboard", 302)
-	}
-}
-
-type googleUserInfo struct {
-	Email         string `json:"email"`
-	EmailVerified bool   `json:"email_verified"`
-	HD            string `json:"hd"`
-	Sub           string `json:"sub"`
-}
-
-func (app *App) upsertGoogleUserFromToken(tok *oauth2.Token) (*models.User, error) {
-	gInfo, err := getGoogleUserInfoFromToken(tok)
-
-	if err != nil {
-		return nil, err
-	}
-
-	// if the app has a restricted domain, check the `hd` query param
-	if app.ServerConf.GoogleRestrictedDomain != "" {
-		if gInfo.HD != app.ServerConf.GoogleRestrictedDomain {
-			return nil, fmt.Errorf("Email is not in the restricted domain group.")
-		}
-	}
-
-	user, err := app.Repo.User().ReadUserByGoogleUserID(gInfo.Sub)
-
-	// if the user does not exist, create new user
-	if err != nil && err == gorm.ErrRecordNotFound {
-		// check if a user with that email address already exists
-		_, err = app.Repo.User().ReadUserByEmail(gInfo.Email)
-
-		if err == gorm.ErrRecordNotFound {
-			user = &models.User{
-				Email:         gInfo.Email,
-				EmailVerified: !app.Capabilities.Email || gInfo.EmailVerified,
-				GoogleUserID:  gInfo.Sub,
-			}
-
-			user, err = app.Repo.User().CreateUser(user)
-
-			if err != nil {
-				return nil, err
-			}
-		} else if err == nil {
-			return nil, fmt.Errorf("email already registered")
-		} else if err != nil {
-			return nil, err
-		}
-	} else if err != nil {
-		return nil, fmt.Errorf("unexpected error occurred:%s", err.Error())
-	}
-
-	return user, nil
-}
-
-func getGoogleUserInfoFromToken(tok *oauth2.Token) (*googleUserInfo, error) {
-	// use userinfo endpoint for Google OIDC to get claims
-	url := "https://openidconnect.googleapis.com/v1/userinfo"
-
-	req, err := http.NewRequest("GET", url, nil)
-
-	req.Header.Add("Authorization", "Bearer "+tok.AccessToken)
-
-	client := &http.Client{}
-
-	response, err := client.Do(req)
-
-	if err != nil {
-		return nil, fmt.Errorf("failed getting user info: %s", err.Error())
-	}
-
-	defer response.Body.Close()
-
-	contents, err := ioutil.ReadAll(response.Body)
-
-	if err != nil {
-		return nil, fmt.Errorf("failed reading response body: %s", err.Error())
-	}
-
-	// parse contents into Google userinfo claims
-	gInfo := &googleUserInfo{}
-	err = json.Unmarshal(contents, &gInfo)
-
-	return gInfo, nil
-}

+ 0 - 191
server/api/oauth_slack_handler.go

@@ -1,191 +0,0 @@
-package api
-
-import (
-	"encoding/json"
-	"fmt"
-	"net/http"
-	"strconv"
-
-	"github.com/go-chi/chi"
-	"github.com/porter-dev/porter/internal/integrations/slack"
-	"github.com/porter-dev/porter/internal/models/integrations"
-	"github.com/porter-dev/porter/internal/oauth"
-	"golang.org/x/oauth2"
-)
-
-// HandleSlackOAuthStartProject starts the oauth2 flow for a project slack request.
-// In this handler, the project id gets written to the session (along with the oauth
-// state param), so that the correct project id can be identified in the callback.
-func (app *App) HandleSlackOAuthStartProject(w http.ResponseWriter, r *http.Request) {
-	state := oauth.CreateRandomState()
-
-	err := app.populateOAuthSession(w, r, state, true)
-
-	if err != nil {
-		app.handleErrorDataRead(err, w)
-		return
-	}
-
-	// specify access type offline to get a refresh token
-	url := app.SlackConf.AuthCodeURL(state, oauth2.AccessTypeOffline)
-
-	http.Redirect(w, r, url, 302)
-}
-
-// HandleSlackOAuthCallback verifies the callback request by checking that the
-// state parameter has not been modified, and validates the token.
-func (app *App) HandleSlackOAuthCallback(w http.ResponseWriter, r *http.Request) {
-	session, err := app.Store.Get(r, app.ServerConf.CookieName)
-
-	if err != nil {
-		app.handleErrorDataRead(err, w)
-		return
-	}
-
-	if _, ok := session.Values["state"]; !ok {
-		app.sendExternalError(
-			err,
-			http.StatusForbidden,
-			HTTPError{
-				Code: http.StatusForbidden,
-				Errors: []string{
-					"Could not read cookie: are cookies enabled?",
-				},
-			},
-			w,
-		)
-
-		return
-	}
-
-	if r.URL.Query().Get("state") != session.Values["state"] {
-		http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
-		return
-	}
-
-	token, err := app.SlackConf.Exchange(oauth2.NoContext, r.URL.Query().Get("code"))
-
-	if err != nil {
-		fmt.Println("ERR IS", err)
-		return
-	}
-
-	userID, _ := session.Values["user_id"].(uint)
-	projID, _ := session.Values["project_id"].(uint)
-
-	slackInt, err := slack.TokenToSlackIntegration(token)
-
-	if err != nil {
-		app.handleErrorDataRead(err, w)
-		return
-	}
-
-	slackInt.UserID = userID
-	slackInt.ProjectID = projID
-
-	// save to repository
-	slackInt, err = app.Repo.SlackIntegration().CreateSlackIntegration(slackInt)
-
-	if err != nil {
-		app.handleErrorDataWrite(err, w)
-		return
-	}
-
-	if session.Values["query_params"] != "" {
-		http.Redirect(w, r, fmt.Sprintf("/dashboard?%s", session.Values["query_params"]), 302)
-	} else {
-		http.Redirect(w, r, "/dashboard", 302)
-	}
-}
-
-// HandleListSlackIntegrations lists all slack integrations belonging to a certain project
-// ID
-func (app *App) HandleListSlackIntegrations(w http.ResponseWriter, r *http.Request) {
-	projID, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
-
-	if err != nil || projID == 0 {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	slackInts, err := app.Repo.SlackIntegration().ListSlackIntegrationsByProjectID(uint(projID))
-
-	if err != nil {
-		app.handleErrorRead(err, ErrProjectDataRead, w)
-		return
-	}
-
-	extSlackInts := make([]*integrations.SlackIntegrationExternal, 0)
-
-	for _, slackInt := range slackInts {
-		extSlackInts = append(extSlackInts, slackInt.Externalize())
-	}
-
-	w.WriteHeader(http.StatusOK)
-
-	if err := json.NewEncoder(w).Encode(extSlackInts); err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-}
-
-// HandleSlackIntegrationExists does 200 if at least one slack integration exists and 404 otherwise
-func (app *App) HandleSlackIntegrationExists(w http.ResponseWriter, r *http.Request) {
-	projID, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
-
-	if err != nil || projID == 0 {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	slackInts, err := app.Repo.SlackIntegration().ListSlackIntegrationsByProjectID(uint(projID))
-
-	if err != nil {
-		app.handleErrorRead(err, ErrProjectDataRead, w)
-		return
-	}
-
-	if len(slackInts) != 0 {
-		w.WriteHeader(http.StatusOK)
-	} else {
-		w.WriteHeader(http.StatusNotFound)
-	}
-}
-
-// HandleDeleteSlackIntegration deletes a slack integration for a project by ID
-func (app *App) HandleDeleteSlackIntegration(w http.ResponseWriter, r *http.Request) {
-	// check that slack integration belongs to given project
-	projID, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
-
-	if err != nil || projID == 0 {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	integrationID, err := strconv.ParseUint(chi.URLParam(r, "slack_integration_id"), 0, 64)
-
-	if err != nil || projID == 0 {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	slackInts, err := app.Repo.SlackIntegration().ListSlackIntegrationsByProjectID(uint(projID))
-
-	if err != nil {
-		app.handleErrorRead(err, ErrProjectDataRead, w)
-		return
-	}
-
-	for _, slackInt := range slackInts {
-		if slackInt.ID == uint(integrationID) {
-			err = app.Repo.SlackIntegration().DeleteSlackIntegration(slackInt.ID)
-			if err != nil {
-				app.handleErrorInternal(err, w)
-				return
-			}
-			w.WriteHeader(http.StatusOK)
-		}
-	}
-
-	w.WriteHeader(http.StatusNotFound)
-}

+ 0 - 348
server/api/project_handler.go

@@ -1,348 +0,0 @@
-package api
-
-import (
-	"encoding/json"
-	"net/http"
-	"strconv"
-
-	"github.com/go-chi/chi"
-	"github.com/porter-dev/porter/api/types"
-	"github.com/porter-dev/porter/internal/analytics"
-	"github.com/porter-dev/porter/internal/forms"
-	"github.com/porter-dev/porter/internal/models"
-)
-
-// Enumeration of user API error codes, represented as int64
-const (
-	ErrProjectDecode ErrorCode = iota + 600
-	ErrProjectValidateFields
-	ErrProjectDataRead
-)
-
-// HandleCreateProject validates a project form entry, converts the project to a gorm
-// model, and saves the user to the database
-func (app *App) HandleCreateProject(w http.ResponseWriter, r *http.Request) {
-	session, err := app.Store.Get(r, app.ServerConf.CookieName)
-
-	if err != nil {
-		http.Error(w, err.Error(), http.StatusInternalServerError)
-		return
-	}
-
-	userID, _ := session.Values["user_id"].(uint)
-
-	form := &forms.CreateProjectForm{}
-
-	// decode from JSON to form value
-	if err := json.NewDecoder(r.Body).Decode(form); err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	// validate the form
-	if err := app.validator.Struct(form); err != nil {
-		app.handleErrorFormValidation(err, ErrProjectValidateFields, w)
-		return
-	}
-
-	// convert the form to a project model
-	projModel, err := form.ToProject(app.Repo.Project())
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	// handle write to the database
-	projModel, err = app.Repo.Project().CreateProject(projModel)
-
-	if err != nil {
-		app.handleErrorDataWrite(err, w)
-		return
-	}
-
-	// create a new Role with the user as the admin
-	_, err = app.Repo.Project().CreateProjectRole(projModel, &models.Role{
-		Role: types.Role{
-			UserID:    userID,
-			ProjectID: projModel.ID,
-			Kind:      types.RoleAdmin,
-		},
-	})
-
-	if err != nil {
-		app.handleErrorDataWrite(err, w)
-		return
-	}
-
-	app.AnalyticsClient.Track(analytics.ProjectCreateTrack(&analytics.ProjectCreateTrackOpts{
-		ProjectScopedTrackOpts: analytics.GetProjectScopedTrackOpts(userID, projModel.ID),
-	}))
-
-	app.Logger.Info().Msgf("New project created: %d", projModel.ID)
-
-	w.WriteHeader(http.StatusCreated)
-
-	projExt := projModel.ToProjectType()
-
-	if err := json.NewEncoder(w).Encode(projExt); err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-}
-
-// HandleGetProjectRoles lists the roles available to the project. For now, these
-// roles are static.
-func (app *App) HandleGetProjectRoles(w http.ResponseWriter, r *http.Request) {
-	roles := []string{models.RoleAdmin, models.RoleDeveloper, models.RoleViewer}
-
-	w.WriteHeader(http.StatusOK)
-
-	if err := json.NewEncoder(w).Encode(&roles); err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-}
-
-type Collaborator struct {
-	ID        uint   `json:"id"`
-	Kind      string `json:"kind"`
-	UserID    uint   `json:"user_id"`
-	Email     string `json:"email"`
-	ProjectID uint   `json:"project_id"`
-}
-
-// HandleListProjectCollaborators lists the collaborators in the project
-func (app *App) HandleListProjectCollaborators(w http.ResponseWriter, r *http.Request) {
-	id, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
-
-	if err != nil || id == 0 {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	roles, err := app.Repo.Project().ListProjectRoles(uint(id))
-
-	if err != nil {
-		app.handleErrorRead(err, ErrProjectDataRead, w)
-		return
-	}
-
-	res := make([]*Collaborator, 0)
-	roleMap := make(map[uint]*models.Role)
-	idArr := make([]uint, 0)
-
-	for _, role := range roles {
-		roleCp := role
-		roleMap[role.UserID] = &roleCp
-		idArr = append(idArr, role.UserID)
-	}
-
-	users, err := app.Repo.User().ListUsersByIDs(idArr)
-
-	if err != nil {
-		app.handleErrorRead(err, ErrProjectDataRead, w)
-		return
-	}
-
-	for _, user := range users {
-		res = append(res, &Collaborator{
-			ID:        roleMap[user.ID].ID,
-			Kind:      string(roleMap[user.ID].Kind),
-			UserID:    roleMap[user.ID].UserID,
-			Email:     user.Email,
-			ProjectID: roleMap[user.ID].ProjectID,
-		})
-	}
-
-	w.WriteHeader(http.StatusOK)
-
-	if err := json.NewEncoder(w).Encode(res); err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-}
-
-// HandleReadProject returns an externalized Project (models.ProjectExternal)
-// based on an ID
-func (app *App) HandleReadProject(w http.ResponseWriter, r *http.Request) {
-	id, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
-
-	if err != nil || id == 0 {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	proj, err := app.Repo.Project().ReadProject(uint(id))
-
-	if err != nil {
-		app.handleErrorRead(err, ErrProjectDataRead, w)
-		return
-	}
-
-	projExt := proj.ToProjectType()
-
-	w.WriteHeader(http.StatusOK)
-
-	if err := json.NewEncoder(w).Encode(projExt); err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-}
-
-// HandleReadProjectPolicy returns the policy document given the current user
-func (app *App) HandleReadProjectPolicy(w http.ResponseWriter, r *http.Request) {
-	id, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
-
-	if err != nil || id == 0 {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	userID, err := app.getUserIDFromRequest(r)
-
-	if err != nil {
-		app.handleErrorInternal(err, w)
-		return
-	}
-
-	role, err := app.Repo.Project().ReadProjectRole(uint(id), userID)
-
-	if err != nil {
-		app.handleErrorRead(err, ErrProjectDataRead, w)
-		return
-	}
-
-	// case on the role to get the policy document
-	var policy types.Policy
-	switch role.Kind {
-	case types.RoleAdmin:
-		policy = types.AdminPolicy
-	case types.RoleDeveloper:
-		policy = types.DeveloperPolicy
-	case types.RoleViewer:
-		policy = types.ViewerPolicy
-	}
-
-	if err := json.NewEncoder(w).Encode(policy); err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-}
-
-// HandleUpdateProjectRole updates a project role with a new "kind"
-func (app *App) HandleUpdateProjectRole(w http.ResponseWriter, r *http.Request) {
-	id, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
-
-	if err != nil || id == 0 {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	userID, err := strconv.ParseUint(chi.URLParam(r, "user_id"), 0, 64)
-
-	if err != nil || id == 0 {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	role, err := app.Repo.Project().ReadProjectRole(uint(id), uint(userID))
-
-	if err != nil {
-		http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
-		return
-	}
-
-	form := &forms.UpdateProjectRoleForm{}
-
-	// decode from JSON to form value
-	if err := json.NewDecoder(r.Body).Decode(form); err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	role.Kind = types.RoleKind(form.Kind)
-
-	role, err = app.Repo.Project().UpdateProjectRole(uint(id), role)
-
-	if err != nil {
-		app.handleErrorRead(err, ErrProjectDataRead, w)
-		return
-	}
-
-	if err := json.NewEncoder(w).Encode(role.Externalize()); err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-}
-
-// HandleDeleteProject deletes a project from the db, reading from the project_id
-// in the URL param
-func (app *App) HandleDeleteProject(w http.ResponseWriter, r *http.Request) {
-	id, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
-
-	if err != nil || id == 0 {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	proj, err := app.Repo.Project().ReadProject(uint(id))
-
-	if err != nil {
-		app.handleErrorRead(err, ErrProjectDataRead, w)
-		return
-	}
-
-	proj, err = app.Repo.Project().DeleteProject(proj)
-
-	if err != nil {
-		app.handleErrorRead(err, ErrProjectDataRead, w)
-		return
-	}
-
-	projExternal := proj.ToProjectType()
-
-	w.WriteHeader(http.StatusOK)
-
-	if err := json.NewEncoder(w).Encode(projExternal); err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-}
-
-// HandleDeleteProjectRole deletes a project role from the db, reading from the project_id
-// in the URL param
-func (app *App) HandleDeleteProjectRole(w http.ResponseWriter, r *http.Request) {
-	id, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
-
-	if err != nil || id == 0 {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	userID, err := strconv.ParseUint(chi.URLParam(r, "user_id"), 0, 64)
-
-	if err != nil || id == 0 {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	role, err := app.Repo.Project().ReadProjectRole(uint(id), uint(userID))
-
-	if err != nil {
-		http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
-		return
-	}
-
-	role, err = app.Repo.Project().DeleteProjectRole(uint(id), uint(userID))
-
-	if err != nil {
-		app.handleErrorRead(err, ErrProjectDataRead, w)
-		return
-	}
-
-	if err := json.NewEncoder(w).Encode(role.Externalize()); err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-}

+ 0 - 184
server/api/project_handler_test.go

@@ -1,184 +0,0 @@
-package api_test
-
-// import (
-// 	"encoding/json"
-// 	"net/http"
-// 	"strings"
-// 	"testing"
-
-// 	"github.com/go-test/deep"
-// 	"github.com/porter-dev/porter/api/types"
-// 	"github.com/porter-dev/porter/internal/models"
-// )
-
-// // ------------------------- TEST TYPES AND MAIN LOOP ------------------------- //
-
-// type projTest struct {
-// 	initializers []func(t *tester)
-// 	msg          string
-// 	method       string
-// 	endpoint     string
-// 	body         string
-// 	expStatus    int
-// 	expBody      string
-// 	useCookie    bool
-// 	validators   []func(c *projTest, tester *tester, t *testing.T)
-// }
-
-// func testProjRequests(t *testing.T, tests []*projTest, canQuery bool) {
-// 	for _, c := range tests {
-// 		// create a new tester
-// 		tester := newTester(canQuery)
-
-// 		// if there's an initializer, call it
-// 		for _, init := range c.initializers {
-// 			init(tester)
-// 		}
-
-// 		req, err := http.NewRequest(
-// 			c.method,
-// 			c.endpoint,
-// 			strings.NewReader(c.body),
-// 		)
-
-// 		tester.req = req
-
-// 		if c.useCookie {
-// 			req.AddCookie(tester.cookie)
-// 		}
-
-// 		if err != nil {
-// 			t.Fatal(err)
-// 		}
-
-// 		tester.execute()
-// 		rr := tester.rr
-
-// 		// first, check that the status matches
-// 		if status := rr.Code; status != c.expStatus {
-// 			t.Errorf("%s, handler returned wrong status code: got %v want %v",
-// 				c.msg, status, c.expStatus)
-// 		}
-
-// 		// if there's a validator, call it
-// 		for _, validate := range c.validators {
-// 			validate(c, tester, t)
-// 		}
-// 	}
-// }
-
-// // ------------------------- TEST FIXTURES AND FUNCTIONS  ------------------------- //
-
-// var createProjectTests = []*projTest{
-// 	{
-// 		initializers: []func(t *tester){
-// 			initUserDefault,
-// 		},
-// 		msg:      "Create project",
-// 		method:   "POST",
-// 		endpoint: "/api/projects",
-// 		body: `{
-// 			"name": "project-test"
-// 		}`,
-// 		expStatus: http.StatusCreated,
-// 		expBody:   `{"id":1,"name":"project-test","roles":[{"id":0,"kind":"admin","user_id":1,"project_id":1}]}`,
-// 		useCookie: true,
-// 		validators: []func(c *projTest, tester *tester, t *testing.T){
-// 			projectModelBodyValidator,
-// 		},
-// 	},
-// }
-
-// func TestHandleCreateProject(t *testing.T) {
-// 	testProjRequests(t, createProjectTests, true)
-// }
-
-// var readProjectTests = []*projTest{
-// 	{
-// 		initializers: []func(t *tester){
-// 			initUserDefault,
-// 			initProject,
-// 		},
-// 		msg:       "Read project",
-// 		method:    "GET",
-// 		endpoint:  "/api/projects/1",
-// 		body:      ``,
-// 		expStatus: http.StatusOK,
-// 		expBody:   `{"id":1,"name":"project-test","roles":[{"id":0,"kind":"admin","user_id":1,"project_id":1}]}`,
-// 		useCookie: true,
-// 		validators: []func(c *projTest, tester *tester, t *testing.T){
-// 			projectModelBodyValidator,
-// 		},
-// 	},
-// }
-
-// func TestHandleReadProject(t *testing.T) {
-// 	testProjRequests(t, readProjectTests, true)
-// }
-
-// var deleteProjectTests = []*projTest{
-// 	{
-// 		initializers: []func(t *tester){
-// 			initUserDefault,
-// 			initProject,
-// 		},
-// 		msg:       "Delete project",
-// 		method:    "DELETE",
-// 		endpoint:  "/api/projects/1",
-// 		body:      ``,
-// 		expStatus: http.StatusOK,
-// 		expBody:   `{"id":1,"name":"project-test","roles":[{"id":0,"kind":"admin","user_id":1,"project_id":1}]}`,
-// 		useCookie: true,
-// 		validators: []func(c *projTest, tester *tester, t *testing.T){
-// 			projectModelBodyValidator,
-// 		},
-// 	},
-// }
-
-// func TestHandleDeleteProject(t *testing.T) {
-// 	testProjRequests(t, deleteProjectTests, true)
-// }
-
-// // ------------------------- INITIALIZERS AND VALIDATORS ------------------------- //
-
-// func initProject(tester *tester) {
-// 	user, err := tester.repo.User().ReadUserByEmail("belanger@getporter.dev")
-
-// 	if err != nil {
-// 		panic(err)
-// 	}
-
-// 	// handle write to the database
-// 	projModel, _ := tester.repo.Project().CreateProject(&models.Project{
-// 		Name: "project-test",
-// 	})
-
-// 	// create a new Role with the user as the owner
-// 	tester.repo.Project().CreateProjectRole(projModel, &models.Role{
-// 		Role: types.Role{
-// 			UserID:    user.ID,
-// 			ProjectID: projModel.ID,
-// 			Kind:      types.RoleAdmin,
-// 		},
-// 	})
-// }
-
-// func projectBasicBodyValidator(c *projTest, tester *tester, t *testing.T) {
-// 	if body := tester.rr.Body.String(); strings.TrimSpace(body) != strings.TrimSpace(c.expBody) {
-// 		t.Errorf("%s, handler returned wrong body: got %v want %v",
-// 			c.msg, body, c.expBody)
-// 	}
-// }
-
-// func projectModelBodyValidator(c *projTest, tester *tester, t *testing.T) {
-// 	gotBody := &types.Project{}
-// 	expBody := &types.Project{}
-
-// 	json.Unmarshal(tester.rr.Body.Bytes(), gotBody)
-// 	json.Unmarshal([]byte(c.expBody), expBody)
-
-// 	if diff := deep.Equal(gotBody, expBody); diff != nil {
-// 		t.Errorf("handler returned wrong body:\n")
-// 		t.Error(diff)
-// 	}
-// }

+ 0 - 1146
server/api/provision_handler.go

@@ -1,1146 +0,0 @@
-package api
-
-import (
-	"encoding/json"
-	"net/http"
-	"strconv"
-
-	"github.com/go-chi/chi"
-
-<<<<<<< HEAD
-	"fmt"
-
-	"github.com/porter-dev/porter/api/types"
-	"github.com/porter-dev/porter/internal/adapter"
-=======
->>>>>>> master
-	"github.com/porter-dev/porter/internal/analytics"
-	"github.com/porter-dev/porter/internal/forms"
-	"github.com/porter-dev/porter/internal/kubernetes"
-	"github.com/porter-dev/porter/internal/kubernetes/provisioner"
-)
-
-// HandleProvisionTestInfra will create a test resource by deploying a provisioner
-// container pod
-func (app *App) HandleProvisionTestInfra(w http.ResponseWriter, r *http.Request) {
-	projID, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
-	userID, err := app.getUserIDFromRequest(r)
-
-	if err != nil || projID == 0 {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	form := &forms.CreateTestInfra{
-		ProjectID: uint(projID),
-	}
-
-	// decode from JSON to form value
-	if err := json.NewDecoder(r.Body).Decode(form); err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	// convert the form to an aws infra instance
-	infra, err := form.ToInfra()
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	infra.CreatedByUserID = userID
-
-	// handle write to the database
-	infra, err = app.Repo.Infra().CreateInfra(infra)
-
-	if err != nil {
-		app.handleErrorDataWrite(err, w)
-		return
-	}
-
-	_, err = app.ProvisionerAgent.ProvisionTest(
-		uint(projID),
-		infra,
-		app.Repo,
-		provisioner.Apply,
-		&app.DBConf,
-		app.RedisConf,
-		app.ServerConf.ProvisionerImageTag,
-		app.ServerConf.ProvisionerImagePullSecret,
-	)
-
-	if err != nil {
-		infra.Status = types.StatusError
-		infra, _ = app.Repo.Infra().UpdateInfra(infra)
-
-		app.handleErrorInternal(err, w)
-		return
-	}
-
-	app.Logger.Info().Msgf("New test infra created: %d", infra.ID)
-
-	w.WriteHeader(http.StatusCreated)
-
-	infraExt := infra.ToInfraType()
-
-	if err := json.NewEncoder(w).Encode(infraExt); err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-}
-
-// HandleDestroyTestInfra destroys test infra
-func (app *App) HandleDestroyTestInfra(w http.ResponseWriter, r *http.Request) {
-	// get path parameters
-	infraID, err := strconv.ParseUint(chi.URLParam(r, "infra_id"), 10, 64)
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	// read infra to get id
-	infra, err := app.Repo.Infra().ReadInfra(uint(infraID))
-
-	if err != nil {
-		app.handleErrorDataRead(err, w)
-		return
-	}
-
-	// launch provisioning destruction pod
-	agent, err := kubernetes.GetAgentInClusterConfig()
-
-	if err != nil {
-		infra.Status = types.StatusError
-		infra, _ = app.Repo.Infra().UpdateInfra(infra)
-
-		app.handleErrorDataRead(err, w)
-		return
-	}
-
-	// mark infra for deletion
-	infra.Status = types.StatusDestroying
-	infra, err = app.Repo.Infra().UpdateInfra(infra)
-
-	if err != nil {
-		app.handleErrorDataWrite(err, w)
-		return
-	}
-
-	_, err = agent.ProvisionTest(
-		infra.ProjectID,
-		infra,
-		app.Repo,
-		provisioner.Destroy,
-		&app.DBConf,
-		app.RedisConf,
-		app.ServerConf.ProvisionerImageTag,
-		app.ServerConf.ProvisionerImagePullSecret,
-	)
-
-	if err != nil {
-		app.handleErrorInternal(err, w)
-		return
-	}
-
-	app.Logger.Info().Msgf("Test infra marked for destruction: %d", infra.ID)
-
-	w.WriteHeader(http.StatusOK)
-}
-
-// HandleProvisionAWSECRInfra provisions a new aws ECR instance for a project
-func (app *App) HandleProvisionAWSECRInfra(w http.ResponseWriter, r *http.Request) {
-	projID, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
-	userID, err := app.getUserIDFromRequest(r)
-
-	if err != nil || projID == 0 {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	form := &forms.CreateECRInfra{
-		ProjectID: uint(projID),
-	}
-
-	// decode from JSON to form value
-	if err := json.NewDecoder(r.Body).Decode(form); err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	// validate the form
-	if err := app.validator.Struct(form); err != nil {
-		app.handleErrorFormValidation(err, ErrProjectValidateFields, w)
-		return
-	}
-
-	// convert the form to an aws infra instance
-	infra, err := form.ToInfra()
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	infra.CreatedByUserID = userID
-
-	// handle write to the database
-	infra, err = app.Repo.Infra().CreateInfra(infra)
-
-	if err != nil {
-		app.handleErrorDataWrite(err, w)
-		return
-	}
-
-	awsInt, err := app.Repo.AWSIntegration().ReadAWSIntegration(infra.AWSIntegrationID)
-
-	if err != nil {
-		infra.Status = types.StatusError
-		infra, _ = app.Repo.Infra().UpdateInfra(infra)
-
-		app.handleErrorDataRead(err, w)
-		return
-	}
-
-	// launch provisioning pod
-	_, err = app.ProvisionerAgent.ProvisionECR(
-		uint(projID),
-		awsInt,
-		form.ECRName,
-		app.Repo,
-		infra,
-		provisioner.Apply,
-		&app.DBConf,
-		app.RedisConf,
-		app.ServerConf.ProvisionerImageTag,
-		app.ServerConf.ProvisionerImagePullSecret,
-	)
-
-	if err != nil {
-		infra.Status = types.StatusError
-		infra, _ = app.Repo.Infra().UpdateInfra(infra)
-
-		app.handleErrorInternal(err, w)
-		return
-	}
-
-	app.Logger.Info().Msgf("New aws ecr infra created: %d", infra.ID)
-
-	app.AnalyticsClient.Track(analytics.RegistryProvisioningStartTrack(
-		&analytics.RegistryProvisioningStartTrackOpts{
-			ProjectScopedTrackOpts: analytics.GetProjectScopedTrackOpts(userID, uint(projID)),
-			RegistryType:           models.InfraECR,
-			InfraID:                infra.ID,
-		},
-	))
-
-	w.WriteHeader(http.StatusCreated)
-
-	infraExt := infra.ToInfraType()
-
-	if err := json.NewEncoder(w).Encode(infraExt); err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-}
-
-// HandleDestroyAWSECRInfra destroys ecr infra
-func (app *App) HandleDestroyAWSECRInfra(w http.ResponseWriter, r *http.Request) {
-	// get path parameters
-	infraID, err := strconv.ParseUint(chi.URLParam(r, "infra_id"), 10, 64)
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	// read infra to get id
-	infra, err := app.Repo.Infra().ReadInfra(uint(infraID))
-
-	if err != nil {
-		app.handleErrorDataRead(err, w)
-		return
-	}
-
-	awsInt, err := app.Repo.AWSIntegration().ReadAWSIntegration(infra.AWSIntegrationID)
-
-	form := &forms.DestroyECRInfra{}
-
-	// decode from JSON to form value
-	if err := json.NewDecoder(r.Body).Decode(form); err != nil {
-		infra.Status = types.StatusError
-		infra, _ = app.Repo.Infra().UpdateInfra(infra)
-
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	// validate the form
-	if err := app.validator.Struct(form); err != nil {
-		infra.Status = types.StatusError
-		infra, _ = app.Repo.Infra().UpdateInfra(infra)
-
-		app.handleErrorFormValidation(err, ErrProjectValidateFields, w)
-		return
-	}
-
-	// launch provisioning destruction pod
-	// mark infra for deletion
-	infra.Status = types.StatusDestroying
-	infra, err = app.Repo.Infra().UpdateInfra(infra)
-
-	if err != nil {
-		app.handleErrorDataWrite(err, w)
-		return
-	}
-
-	_, err = app.ProvisionerAgent.ProvisionECR(
-		infra.ProjectID,
-		awsInt,
-		form.ECRName,
-		app.Repo,
-		infra,
-		provisioner.Destroy,
-		&app.DBConf,
-		app.RedisConf,
-		app.ServerConf.ProvisionerImageTag,
-		app.ServerConf.ProvisionerImagePullSecret,
-	)
-
-	if err != nil {
-		app.handleErrorInternal(err, w)
-		return
-	}
-
-	app.Logger.Info().Msgf("AWS ECR infra marked for destruction: %d", infra.ID)
-
-	w.WriteHeader(http.StatusOK)
-}
-
-// HandleProvisionAWSEKSInfra provisions a new aws EKS instance for a project
-func (app *App) HandleProvisionAWSEKSInfra(w http.ResponseWriter, r *http.Request) {
-	projID, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
-	userID, err := app.getUserIDFromRequest(r)
-
-	if err != nil || projID == 0 {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	form := &forms.CreateEKSInfra{
-		ProjectID: uint(projID),
-	}
-
-	// decode from JSON to form value
-	if err := json.NewDecoder(r.Body).Decode(form); err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	// validate the form
-	if err := app.validator.Struct(form); err != nil {
-		app.handleErrorFormValidation(err, ErrProjectValidateFields, w)
-		return
-	}
-
-	// convert the form to an aws infra instance
-	infra, err := form.ToInfra()
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	infra.CreatedByUserID = userID
-
-	// handle write to the database
-	infra, err = app.Repo.Infra().CreateInfra(infra)
-
-	if err != nil {
-		app.handleErrorDataWrite(err, w)
-		return
-	}
-
-	awsInt, err := app.Repo.AWSIntegration().ReadAWSIntegration(infra.AWSIntegrationID)
-
-	if err != nil {
-		infra.Status = types.StatusError
-		infra, _ = app.Repo.Infra().UpdateInfra(infra)
-
-		app.handleErrorDataRead(err, w)
-		return
-	}
-
-	// launch provisioning pod
-	_, err = app.ProvisionerAgent.ProvisionEKS(
-		uint(projID),
-		awsInt,
-		form.EKSName,
-		form.MachineType,
-		app.Repo,
-		infra,
-		provisioner.Apply,
-		&app.DBConf,
-		app.RedisConf,
-		app.ServerConf.ProvisionerImageTag,
-		app.ServerConf.ProvisionerImagePullSecret,
-	)
-
-	if err != nil {
-		infra.Status = types.StatusError
-		infra, _ = app.Repo.Infra().UpdateInfra(infra)
-
-		app.handleErrorInternal(err, w)
-		return
-	}
-
-	app.Logger.Info().Msgf("New aws eks infra created: %d", infra.ID)
-
-	app.AnalyticsClient.Track(analytics.ClusterProvisioningStartTrack(
-		&analytics.ClusterProvisioningStartTrackOpts{
-			ProjectScopedTrackOpts: analytics.GetProjectScopedTrackOpts(userID, uint(projID)),
-			ClusterType:            models.InfraEKS,
-			InfraID:                infra.ID,
-		},
-	))
-
-	w.WriteHeader(http.StatusCreated)
-
-	infraExt := infra.ToInfraType()
-
-	if err := json.NewEncoder(w).Encode(infraExt); err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-}
-
-// HandleDestroyAWSEKSInfra destroys eks infra
-func (app *App) HandleDestroyAWSEKSInfra(w http.ResponseWriter, r *http.Request) {
-	// get path parameters
-	infraID, err := strconv.ParseUint(chi.URLParam(r, "infra_id"), 10, 64)
-	// userID, err := app.getUserIDFromRequest(r)
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	// read infra to get id
-	infra, err := app.Repo.Infra().ReadInfra(uint(infraID))
-
-	if err != nil {
-		app.handleErrorDataRead(err, w)
-		return
-	}
-
-	awsInt, err := app.Repo.AWSIntegration().ReadAWSIntegration(infra.AWSIntegrationID)
-
-	form := &forms.DestroyEKSInfra{}
-
-	// decode from JSON to form value
-	if err := json.NewDecoder(r.Body).Decode(form); err != nil {
-		infra.Status = types.StatusError
-		infra, _ = app.Repo.Infra().UpdateInfra(infra)
-
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	// validate the form
-	if err := app.validator.Struct(form); err != nil {
-		infra.Status = types.StatusError
-		infra, _ = app.Repo.Infra().UpdateInfra(infra)
-
-		app.handleErrorFormValidation(err, ErrProjectValidateFields, w)
-		return
-	}
-
-	// launch provisioning destruction pod
-	// mark infra for deletion
-	infra.Status = types.StatusDestroying
-	infra, err = app.Repo.Infra().UpdateInfra(infra)
-
-	if err != nil {
-		app.handleErrorDataWrite(err, w)
-		return
-	}
-
-	_, err = app.ProvisionerAgent.ProvisionEKS(
-		infra.ProjectID,
-		awsInt,
-		form.EKSName,
-		"",
-		app.Repo,
-		infra,
-		provisioner.Destroy,
-		&app.DBConf,
-		app.RedisConf,
-		app.ServerConf.ProvisionerImageTag,
-		app.ServerConf.ProvisionerImagePullSecret,
-	)
-
-	if err != nil {
-		app.handleErrorInternal(err, w)
-		return
-	}
-
-	app.Logger.Info().Msgf("AWS EKS infra marked for destruction: %d", infra.ID)
-
-	w.WriteHeader(http.StatusOK)
-}
-
-// HandleProvisionGCPGCRInfra enables GCR for a project
-func (app *App) HandleProvisionGCPGCRInfra(w http.ResponseWriter, r *http.Request) {
-	projID, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
-	userID, err := app.getUserIDFromRequest(r)
-
-	if err != nil || projID == 0 {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	form := &forms.CreateGCRInfra{
-		ProjectID: uint(projID),
-	}
-
-	// decode from JSON to form value
-	if err := json.NewDecoder(r.Body).Decode(form); err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	// validate the form
-	if err := app.validator.Struct(form); err != nil {
-		app.handleErrorFormValidation(err, ErrProjectValidateFields, w)
-		return
-	}
-
-	// convert the form to an aws infra instance
-	infra, err := form.ToInfra()
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	infra.CreatedByUserID = userID
-
-	// handle write to the database
-	infra, err = app.Repo.Infra().CreateInfra(infra)
-
-	if err != nil {
-		app.handleErrorDataWrite(err, w)
-		return
-	}
-
-	gcpInt, err := app.Repo.GCPIntegration().ReadGCPIntegration(infra.GCPIntegrationID)
-
-	if err != nil {
-		infra.Status = types.StatusError
-		infra, _ = app.Repo.Infra().UpdateInfra(infra)
-
-		app.handleErrorDataRead(err, w)
-		return
-	}
-
-	// launch provisioning pod
-	_, err = app.ProvisionerAgent.ProvisionGCR(
-		uint(projID),
-		gcpInt,
-		app.Repo,
-		infra,
-		provisioner.Apply,
-		&app.DBConf,
-		app.RedisConf,
-		app.ServerConf.ProvisionerImageTag,
-		app.ServerConf.ProvisionerImagePullSecret,
-	)
-
-	if err != nil {
-		infra.Status = types.StatusError
-		infra, _ = app.Repo.Infra().UpdateInfra(infra)
-
-		app.handleErrorInternal(err, w)
-		return
-	}
-
-	app.Logger.Info().Msgf("New gcp gcr infra created: %d", infra.ID)
-
-	app.AnalyticsClient.Track(analytics.RegistryProvisioningStartTrack(
-		&analytics.RegistryProvisioningStartTrackOpts{
-			ProjectScopedTrackOpts: analytics.GetProjectScopedTrackOpts(userID, uint(projID)),
-			RegistryType:           models.InfraGCR,
-			InfraID:                infra.ID,
-		},
-	))
-
-	w.WriteHeader(http.StatusCreated)
-
-	infraExt := infra.ToInfraType()
-
-	if err := json.NewEncoder(w).Encode(infraExt); err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-}
-
-// HandleProvisionGCPGKEInfra provisions a new GKE instance for a project
-func (app *App) HandleProvisionGCPGKEInfra(w http.ResponseWriter, r *http.Request) {
-	projID, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
-	userID, err := app.getUserIDFromRequest(r)
-
-	if err != nil || projID == 0 {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	form := &forms.CreateGKEInfra{
-		ProjectID: uint(projID),
-	}
-
-	// decode from JSON to form value
-	if err := json.NewDecoder(r.Body).Decode(form); err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	// validate the form
-	if err := app.validator.Struct(form); err != nil {
-		app.handleErrorFormValidation(err, ErrProjectValidateFields, w)
-		return
-	}
-
-	// convert the form to an aws infra instance
-	infra, err := form.ToInfra()
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	infra.CreatedByUserID = userID
-
-	// handle write to the database
-	infra, err = app.Repo.Infra().CreateInfra(infra)
-
-	if err != nil {
-		app.handleErrorDataWrite(err, w)
-		return
-	}
-
-	gcpInt, err := app.Repo.GCPIntegration().ReadGCPIntegration(infra.GCPIntegrationID)
-
-	if err != nil {
-		infra.Status = types.StatusError
-		infra, _ = app.Repo.Infra().UpdateInfra(infra)
-
-		app.handleErrorDataRead(err, w)
-		return
-	}
-
-	// launch provisioning pod
-	_, err = app.ProvisionerAgent.ProvisionGKE(
-		uint(projID),
-		gcpInt,
-		form.GKEName,
-		app.Repo,
-		infra,
-		provisioner.Apply,
-		&app.DBConf,
-		app.RedisConf,
-		app.ServerConf.ProvisionerImageTag,
-		app.ServerConf.ProvisionerImagePullSecret,
-	)
-
-	if err != nil {
-		infra.Status = types.StatusError
-		infra, _ = app.Repo.Infra().UpdateInfra(infra)
-
-		app.handleErrorInternal(err, w)
-		return
-	}
-
-	app.Logger.Info().Msgf("New gcp gke infra created: %d", infra.ID)
-
-	app.AnalyticsClient.Track(analytics.ClusterProvisioningStartTrack(
-		&analytics.ClusterProvisioningStartTrackOpts{
-			ProjectScopedTrackOpts: analytics.GetProjectScopedTrackOpts(userID, uint(projID)),
-			ClusterType:            models.InfraGKE,
-			InfraID:                infra.ID,
-		},
-	))
-
-	w.WriteHeader(http.StatusCreated)
-
-	infraExt := infra.ToInfraType()
-
-	if err := json.NewEncoder(w).Encode(infraExt); err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-}
-
-// HandleDestroyGCPGKEInfra destroys gke infra
-func (app *App) HandleDestroyGCPGKEInfra(w http.ResponseWriter, r *http.Request) {
-	// get path parameters
-	infraID, err := strconv.ParseUint(chi.URLParam(r, "infra_id"), 10, 64)
-	// userID, err := app.getUserIDFromRequest(r)
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	// read infra to get id
-	infra, err := app.Repo.Infra().ReadInfra(uint(infraID))
-
-	if err != nil {
-		app.handleErrorDataRead(err, w)
-		return
-	}
-
-	gcpInt, err := app.Repo.GCPIntegration().ReadGCPIntegration(infra.GCPIntegrationID)
-
-	form := &forms.DestroyGKEInfra{}
-
-	// decode from JSON to form value
-	if err := json.NewDecoder(r.Body).Decode(form); err != nil {
-		infra.Status = types.StatusError
-		infra, _ = app.Repo.Infra().UpdateInfra(infra)
-
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	// validate the form
-	if err := app.validator.Struct(form); err != nil {
-		infra.Status = types.StatusError
-		infra, _ = app.Repo.Infra().UpdateInfra(infra)
-
-		app.handleErrorFormValidation(err, ErrProjectValidateFields, w)
-		return
-	}
-
-	// launch provisioning destruction pod
-	// mark infra for deletion
-	infra.Status = types.StatusDestroying
-	infra, err = app.Repo.Infra().UpdateInfra(infra)
-
-	if err != nil {
-		app.handleErrorDataWrite(err, w)
-		return
-	}
-
-	_, err = app.ProvisionerAgent.ProvisionGKE(
-		infra.ProjectID,
-		gcpInt,
-		form.GKEName,
-		app.Repo,
-		infra,
-		provisioner.Destroy,
-		&app.DBConf,
-		app.RedisConf,
-		app.ServerConf.ProvisionerImageTag,
-		app.ServerConf.ProvisionerImagePullSecret,
-	)
-
-	if err != nil {
-		app.handleErrorInternal(err, w)
-		return
-	}
-
-	app.Logger.Info().Msgf("GCP GKE infra marked for destruction: %d", infra.ID)
-
-	w.WriteHeader(http.StatusOK)
-}
-
-// HandleGetProvisioningLogs returns real-time logs of the provisioning process via websockets
-func (app *App) HandleGetProvisioningLogs(w http.ResponseWriter, r *http.Request) {
-	// get path parameters
-	infraID, err := strconv.ParseUint(chi.URLParam(r, "infra_id"), 10, 64)
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	// read infra to get id
-	infra, err := app.Repo.Infra().ReadInfra(uint(infraID))
-
-	if err != nil {
-		app.handleErrorDataRead(err, w)
-		return
-	}
-
-	streamName := infra.GetUniqueName()
-
-	upgrader.CheckOrigin = func(r *http.Request) bool { return true }
-
-	// upgrade to websocket.
-	conn, err := upgrader.Upgrade(w, r, nil)
-
-	if err != nil {
-		app.handleErrorUpgradeWebsocket(err, w)
-	}
-
-	client, err := adapter.NewRedisClient(app.RedisConf)
-
-	if err != nil {
-		app.handleErrorInternal(err, w)
-		return
-	}
-
-	err = provisioner.ResourceStream(client, streamName, conn)
-
-	if err != nil {
-		app.handleErrorWebsocketWrite(err, w)
-		return
-	}
-}
-
-// HandleProvisionDODOCRInfra provisions a new digitalocean DOCR instance for a project
-func (app *App) HandleProvisionDODOCRInfra(w http.ResponseWriter, r *http.Request) {
-	projID, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
-	userID, err := app.getUserIDFromRequest(r)
-
-	if err != nil || projID == 0 {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	form := &forms.CreateDOCRInfra{
-		ProjectID: uint(projID),
-	}
-
-	// decode from JSON to form value
-	if err := json.NewDecoder(r.Body).Decode(form); err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	// validate the form
-	if err := app.validator.Struct(form); err != nil {
-		app.handleErrorFormValidation(err, ErrProjectValidateFields, w)
-		return
-	}
-
-	// convert the form to an aws infra instance
-	infra, err := form.ToInfra()
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	infra.CreatedByUserID = userID
-
-	// handle write to the database
-	infra, err = app.Repo.Infra().CreateInfra(infra)
-
-	if err != nil {
-		app.handleErrorDataWrite(err, w)
-		return
-	}
-
-	oauthInt, err := app.Repo.OAuthIntegration().ReadOAuthIntegration(infra.DOIntegrationID)
-
-	if err != nil {
-		infra.Status = types.StatusError
-		infra, _ = app.Repo.Infra().UpdateInfra(infra)
-
-		app.handleErrorDataRead(err, w)
-		return
-	}
-
-	// launch provisioning pod
-	_, err = app.ProvisionerAgent.ProvisionDOCR(
-		uint(projID),
-		oauthInt,
-		app.DOConf,
-		app.Repo,
-		form.DOCRName,
-		form.DOCRSubscriptionTier,
-		infra,
-		provisioner.Apply,
-		&app.DBConf,
-		app.RedisConf,
-		app.ServerConf.ProvisionerImageTag,
-		app.ServerConf.ProvisionerImagePullSecret,
-	)
-
-	if err != nil {
-		infra.Status = types.StatusError
-		infra, _ = app.Repo.Infra().UpdateInfra(infra)
-
-		app.handleErrorInternal(err, w)
-		return
-	}
-
-	app.Logger.Info().Msgf("New do docr infra created: %d", infra.ID)
-
-	app.AnalyticsClient.Track(analytics.RegistryProvisioningStartTrack(
-		&analytics.RegistryProvisioningStartTrackOpts{
-			ProjectScopedTrackOpts: analytics.GetProjectScopedTrackOpts(userID, uint(projID)),
-			RegistryType:           models.InfraDOCR,
-			InfraID:                infra.ID,
-		},
-	))
-
-	w.WriteHeader(http.StatusCreated)
-
-	infraExt := infra.ToInfraType()
-
-	if err := json.NewEncoder(w).Encode(infraExt); err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-}
-
-// HandleDestroyAWSDOCRInfra destroys docr infra
-func (app *App) HandleDestroyDODOCRInfra(w http.ResponseWriter, r *http.Request) {
-	// get path parameters
-	infraID, err := strconv.ParseUint(chi.URLParam(r, "infra_id"), 10, 64)
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	// read infra to get id
-	infra, err := app.Repo.Infra().ReadInfra(uint(infraID))
-
-	if err != nil {
-		app.handleErrorDataRead(err, w)
-		return
-	}
-
-	oauthInt, err := app.Repo.OAuthIntegration().ReadOAuthIntegration(infra.DOIntegrationID)
-
-	form := &forms.DestroyDOCRInfra{}
-
-	// decode from JSON to form value
-	if err := json.NewDecoder(r.Body).Decode(form); err != nil {
-		infra.Status = types.StatusError
-		infra, _ = app.Repo.Infra().UpdateInfra(infra)
-
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	// validate the form
-	if err := app.validator.Struct(form); err != nil {
-		infra.Status = types.StatusError
-		infra, _ = app.Repo.Infra().UpdateInfra(infra)
-
-		app.handleErrorFormValidation(err, ErrProjectValidateFields, w)
-		return
-	}
-
-	// launch provisioning destruction pod
-	// mark infra for deletion
-	infra.Status = types.StatusDestroying
-	infra, err = app.Repo.Infra().UpdateInfra(infra)
-
-	if err != nil {
-		app.handleErrorDataWrite(err, w)
-		return
-	}
-
-	_, err = app.ProvisionerAgent.ProvisionDOCR(
-		infra.ProjectID,
-		oauthInt,
-		app.DOConf,
-		app.Repo,
-		form.DOCRName,
-		"basic", // this doesn't matter for destroy
-		infra,
-		provisioner.Destroy,
-		&app.DBConf,
-		app.RedisConf,
-		app.ServerConf.ProvisionerImageTag,
-		app.ServerConf.ProvisionerImagePullSecret,
-	)
-
-	if err != nil {
-		app.handleErrorInternal(err, w)
-		return
-	}
-
-	app.Logger.Info().Msgf("DO DOCR infra marked for destruction: %d", infra.ID)
-
-	w.WriteHeader(http.StatusOK)
-}
-
-// HandleProvisionDODOKSInfra provisions a new DO DOKS instance for a project
-func (app *App) HandleProvisionDODOKSInfra(w http.ResponseWriter, r *http.Request) {
-	projID, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
-	userID, err := app.getUserIDFromRequest(r)
-
-	if err != nil || projID == 0 {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	form := &forms.CreateDOKSInfra{
-		ProjectID: uint(projID),
-	}
-
-	// decode from JSON to form value
-	if err := json.NewDecoder(r.Body).Decode(form); err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	// validate the form
-	if err := app.validator.Struct(form); err != nil {
-		app.handleErrorFormValidation(err, ErrProjectValidateFields, w)
-		return
-	}
-
-	// convert the form to an aws infra instance
-	infra, err := form.ToInfra()
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	infra.CreatedByUserID = userID
-
-	// handle write to the database
-	infra, err = app.Repo.Infra().CreateInfra(infra)
-
-	if err != nil {
-		app.handleErrorDataWrite(err, w)
-		return
-	}
-
-	oauthInt, err := app.Repo.OAuthIntegration().ReadOAuthIntegration(infra.DOIntegrationID)
-
-	if err != nil {
-		infra.Status = types.StatusError
-		infra, _ = app.Repo.Infra().UpdateInfra(infra)
-
-		app.handleErrorDataRead(err, w)
-		return
-	}
-
-	// launch provisioning pod
-	_, err = app.ProvisionerAgent.ProvisionDOKS(
-		uint(projID),
-		oauthInt,
-		app.DOConf,
-		app.Repo,
-		form.DORegion,
-		form.DOKSName,
-		infra,
-		provisioner.Apply,
-		&app.DBConf,
-		app.RedisConf,
-		app.ServerConf.ProvisionerImageTag,
-		app.ServerConf.ProvisionerImagePullSecret,
-	)
-
-	if err != nil {
-		infra.Status = types.StatusError
-		infra, _ = app.Repo.Infra().UpdateInfra(infra)
-
-		app.handleErrorInternal(err, w)
-		return
-	}
-
-	app.Logger.Info().Msgf("New do doks infra created: %d", infra.ID)
-
-	app.AnalyticsClient.Track(analytics.ClusterProvisioningStartTrack(
-		&analytics.ClusterProvisioningStartTrackOpts{
-			ProjectScopedTrackOpts: analytics.GetProjectScopedTrackOpts(userID, uint(projID)),
-			ClusterType:            models.InfraDOKS,
-			InfraID:                infra.ID,
-		},
-	))
-
-	w.WriteHeader(http.StatusCreated)
-
-	infraExt := infra.ToInfraType()
-
-	if err := json.NewEncoder(w).Encode(infraExt); err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-}
-
-// HandleDestroyDODOKSInfra destroys DOKS infra
-func (app *App) HandleDestroyDODOKSInfra(w http.ResponseWriter, r *http.Request) {
-	// get path parameters
-	infraID, err := strconv.ParseUint(chi.URLParam(r, "infra_id"), 10, 64)
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	// read infra to get id
-	infra, err := app.Repo.Infra().ReadInfra(uint(infraID))
-
-	if err != nil {
-		app.handleErrorDataRead(err, w)
-		return
-	}
-
-	oauthInt, err := app.Repo.OAuthIntegration().ReadOAuthIntegration(infra.DOIntegrationID)
-
-	form := &forms.DestroyDOKSInfra{}
-
-	// decode from JSON to form value
-	if err := json.NewDecoder(r.Body).Decode(form); err != nil {
-		infra.Status = types.StatusError
-		infra, _ = app.Repo.Infra().UpdateInfra(infra)
-
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	// validate the form
-	if err := app.validator.Struct(form); err != nil {
-		infra.Status = types.StatusError
-		infra, _ = app.Repo.Infra().UpdateInfra(infra)
-
-		app.handleErrorFormValidation(err, ErrProjectValidateFields, w)
-		return
-	}
-
-	// launch provisioning destruction pod
-	// mark infra for deletion
-	infra.Status = types.StatusDestroying
-	infra, err = app.Repo.Infra().UpdateInfra(infra)
-
-	if err != nil {
-		app.handleErrorDataWrite(err, w)
-		return
-	}
-
-	_, err = app.ProvisionerAgent.ProvisionDOKS(
-		infra.ProjectID,
-		oauthInt,
-		app.DOConf,
-		app.Repo,
-		"nyc1",
-		form.DOKSName,
-		infra,
-		provisioner.Destroy,
-		&app.DBConf,
-		app.RedisConf,
-		app.ServerConf.ProvisionerImageTag,
-		app.ServerConf.ProvisionerImagePullSecret,
-	)
-
-	if err != nil {
-		app.handleErrorInternal(err, w)
-		return
-	}
-
-	app.Logger.Info().Msgf("DO DOKS infra marked for destruction: %d", infra.ID)
-
-	w.WriteHeader(http.StatusOK)
-}

+ 0 - 561
server/api/registry_handler.go

@@ -1,561 +0,0 @@
-package api
-
-import (
-	"encoding/base64"
-	"encoding/json"
-	"net/http"
-	"strconv"
-	"strings"
-	"time"
-
-	"github.com/porter-dev/porter/internal/analytics"
-	"github.com/porter-dev/porter/internal/oauth"
-	"github.com/porter-dev/porter/internal/registry"
-
-	"github.com/go-chi/chi"
-	"github.com/porter-dev/porter/internal/forms"
-	"github.com/porter-dev/porter/internal/models"
-
-	"github.com/aws/aws-sdk-go/service/ecr"
-)
-
-// HandleCreateRegistry creates a new registry
-func (app *App) HandleCreateRegistry(w http.ResponseWriter, r *http.Request) {
-	projID, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
-	userID, err := app.getUserIDFromRequest(r)
-	flowID := oauth.CreateRandomState()
-
-	if err != nil || projID == 0 {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	app.AnalyticsClient.Track(analytics.RegistryConnectionStartTrack(
-		&analytics.RegistryConnectionStartTrackOpts{
-			ProjectScopedTrackOpts: analytics.GetProjectScopedTrackOpts(userID, uint(projID)),
-			FlowID:                 flowID,
-		},
-	))
-
-	form := &forms.CreateRegistry{
-		ProjectID: uint(projID),
-	}
-
-	// decode from JSON to form value
-	if err := json.NewDecoder(r.Body).Decode(form); err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	// validate the form
-	if err := app.validator.Struct(form); err != nil {
-		app.handleErrorFormValidation(err, ErrProjectValidateFields, w)
-		return
-	}
-
-	// convert the form to a registry
-	registry, err := form.ToRegistry(app.Repo)
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	// handle write to the database
-	registry, err = app.Repo.Registry().CreateRegistry(registry)
-
-	if err != nil {
-		app.handleErrorDataWrite(err, w)
-		return
-	}
-
-	app.Logger.Info().Msgf("New registry created: %d", registry.ID)
-
-	app.AnalyticsClient.Track(analytics.RegistryConnectionSuccessTrack(
-		&analytics.RegistryConnectionSuccessTrackOpts{
-			RegistryScopedTrackOpts: analytics.GetRegistryScopedTrackOpts(userID, uint(projID), registry.ID),
-			FlowID:                  flowID,
-		},
-	))
-
-	w.WriteHeader(http.StatusCreated)
-
-	regExt := registry.Externalize()
-
-	if err := json.NewEncoder(w).Encode(regExt); err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-}
-
-// HandleCreateRepository creates a new image repository in a registry, if the registry
-// does not allow for create-on-push behavior
-func (app *App) HandleCreateRepository(w http.ResponseWriter, r *http.Request) {
-	projID, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
-
-	if err != nil || projID == 0 {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	regID, err := strconv.ParseUint(chi.URLParam(r, "registry_id"), 0, 64)
-
-	if err != nil || projID == 0 {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	form := &forms.CreateRepository{}
-
-	// decode from JSON to form value
-	if err := json.NewDecoder(r.Body).Decode(form); err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	// validate the form
-	if err := app.validator.Struct(form); err != nil {
-		app.handleErrorFormValidation(err, ErrProjectValidateFields, w)
-		return
-	}
-
-	// read the registry
-	reg, err := app.Repo.Registry().ReadRegistry(uint(regID))
-
-	if err != nil {
-		app.handleErrorDataRead(err, w)
-		return
-	}
-
-	_reg := registry.Registry(*reg)
-	regAPI := &_reg
-
-	// parse the name from the registry
-	nameSpl := strings.Split(form.ImageRepoURI, "/")
-	repoName := nameSpl[len(nameSpl)-1]
-
-	err = regAPI.CreateRepository(app.Repo, repoName)
-
-	if err != nil {
-		app.handleErrorInternal(err, w)
-		return
-	}
-
-	w.WriteHeader(http.StatusCreated)
-}
-
-// HandleListProjectRegistries returns a list of registries for a project
-func (app *App) HandleListProjectRegistries(w http.ResponseWriter, r *http.Request) {
-	projID, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
-
-	if err != nil || projID == 0 {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	regs, err := app.Repo.Registry().ListRegistriesByProjectID(uint(projID))
-
-	if err != nil {
-		app.handleErrorRead(err, ErrProjectDataRead, w)
-		return
-	}
-
-	extRegs := make([]*models.RegistryExternal, 0)
-
-	for _, reg := range regs {
-		extRegs = append(extRegs, reg.Externalize())
-	}
-
-	w.WriteHeader(http.StatusOK)
-
-	if err := json.NewEncoder(w).Encode(extRegs); err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-}
-
-// temp -- token response
-type RegTokenResponse struct {
-	Token     string     `json:"token"`
-	ExpiresAt *time.Time `json:"expires_at"`
-}
-
-// HandleGetProjectRegistryECRToken gets an ECR token for a registry
-func (app *App) HandleGetProjectRegistryECRToken(w http.ResponseWriter, r *http.Request) {
-	projID, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
-
-	if err != nil || projID == 0 {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	region := chi.URLParam(r, "region")
-
-	if region == "" {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	// list registries and find one that matches the region
-	regs, err := app.Repo.Registry().ListRegistriesByProjectID(uint(projID))
-	var token string
-	var expiresAt *time.Time
-
-	for _, reg := range regs {
-		if reg.AWSIntegrationID != 0 {
-			awsInt, err := app.Repo.AWSIntegration().ReadAWSIntegration(reg.AWSIntegrationID)
-
-			if err != nil {
-				app.handleErrorDataRead(err, w)
-				return
-			}
-
-			if awsInt.AWSRegion == region {
-				// get the aws integration and session
-				sess, err := awsInt.GetSession()
-
-				if err != nil {
-					app.handleErrorDataRead(err, w)
-					return
-				}
-
-				ecrSvc := ecr.New(sess)
-
-				output, err := ecrSvc.GetAuthorizationToken(&ecr.GetAuthorizationTokenInput{})
-
-				if err != nil {
-					app.handleErrorDataRead(err, w)
-					return
-				}
-
-				token = *output.AuthorizationData[0].AuthorizationToken
-				expiresAt = output.AuthorizationData[0].ExpiresAt
-			}
-		}
-	}
-
-	resp := &RegTokenResponse{
-		Token:     token,
-		ExpiresAt: expiresAt,
-	}
-
-	w.WriteHeader(http.StatusOK)
-
-	if err := json.NewEncoder(w).Encode(resp); err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-}
-
-// HandleGetProjectRegistryDockerhubToken gets a Dockerhub token for a registry
-func (app *App) HandleGetProjectRegistryDockerhubToken(w http.ResponseWriter, r *http.Request) {
-	projID, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
-
-	if err != nil || projID == 0 {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	// list registries and find one that matches the region
-	regs, err := app.Repo.Registry().ListRegistriesByProjectID(uint(projID))
-	var token string
-	var expiresAt *time.Time
-
-	for _, reg := range regs {
-		if reg.BasicIntegrationID != 0 && strings.Contains(reg.URL, "index.docker.io") {
-			basic, err := app.Repo.BasicIntegration().ReadBasicIntegration(reg.BasicIntegrationID)
-
-			if err != nil {
-				app.handleErrorDataRead(err, w)
-				return
-			}
-
-			token = base64.StdEncoding.EncodeToString([]byte(string(basic.Username) + ":" + string(basic.Password)))
-
-			// we'll just set an arbitrary 30-day expiry time (this is not enforced)
-			timeExpires := time.Now().Add(30 * 24 * 3600 * time.Second)
-			expiresAt = &timeExpires
-		}
-	}
-
-	resp := &RegTokenResponse{
-		Token:     token,
-		ExpiresAt: expiresAt,
-	}
-
-	w.WriteHeader(http.StatusOK)
-
-	if err := json.NewEncoder(w).Encode(resp); err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-}
-
-type GCRTokenRequestBody struct {
-	ServerURL string `json:"server_url"`
-}
-
-// HandleGetProjectRegistryGCRToken gets a GCR token for a registry
-func (app *App) HandleGetProjectRegistryGCRToken(w http.ResponseWriter, r *http.Request) {
-	projID, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
-
-	if err != nil || projID == 0 {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	reqBody := &GCRTokenRequestBody{}
-
-	// decode from JSON to form value
-	if err := json.NewDecoder(r.Body).Decode(reqBody); err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	// list registries and find one that matches the region
-	regs, err := app.Repo.Registry().ListRegistriesByProjectID(uint(projID))
-	var token string
-	var expiresAt *time.Time
-
-	for _, reg := range regs {
-		if reg.GCPIntegrationID != 0 && strings.Contains(reg.URL, reqBody.ServerURL) {
-			_reg := registry.Registry(*reg)
-
-			tokenCache, err := _reg.GetGCRToken(app.Repo)
-
-			if err != nil {
-				app.handleErrorDataRead(err, w)
-				return
-			}
-
-			token = string(tokenCache.Token)
-			expiresAt = &tokenCache.Expiry
-			break
-		}
-	}
-
-	resp := &RegTokenResponse{
-		Token:     token,
-		ExpiresAt: expiresAt,
-	}
-
-	w.WriteHeader(http.StatusOK)
-
-	if err := json.NewEncoder(w).Encode(resp); err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-}
-
-// HandleGetProjectRegistryDOCRToken gets a DOCR token for a registry
-func (app *App) HandleGetProjectRegistryDOCRToken(w http.ResponseWriter, r *http.Request) {
-	projID, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
-
-	if err != nil || projID == 0 {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	reqBody := &GCRTokenRequestBody{}
-
-	// decode from JSON to form value
-	if err := json.NewDecoder(r.Body).Decode(reqBody); err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	// list registries and find one that matches the region
-	regs, err := app.Repo.Registry().ListRegistriesByProjectID(uint(projID))
-	var token string
-	var expiresAt *time.Time
-
-	for _, reg := range regs {
-		if reg.DOIntegrationID != 0 && strings.Contains(reg.URL, reqBody.ServerURL) {
-			oauthInt, err := app.Repo.OAuthIntegration().ReadOAuthIntegration(reg.DOIntegrationID)
-
-			if err != nil {
-				app.handleErrorDataRead(err, w)
-				return
-			}
-
-			tok, expiry, err := oauth.GetAccessToken(oauthInt.SharedOAuthModel, app.DOConf, oauth.MakeUpdateOAuthIntegrationTokenFunction(oauthInt, app.Repo))
-
-			if err != nil {
-				app.handleErrorDataRead(err, w)
-				return
-			}
-
-			token = tok
-			expiresAt = expiry
-			break
-		}
-	}
-
-	resp := &RegTokenResponse{
-		Token:     token,
-		ExpiresAt: expiresAt,
-	}
-
-	w.WriteHeader(http.StatusOK)
-
-	if err := json.NewEncoder(w).Encode(resp); err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-}
-
-// HandleUpdateProjectRegistry updates a registry
-func (app *App) HandleUpdateProjectRegistry(w http.ResponseWriter, r *http.Request) {
-	projID, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
-
-	if err != nil || projID == 0 {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	registryID, err := strconv.ParseUint(chi.URLParam(r, "registry_id"), 0, 64)
-
-	if err != nil || registryID == 0 {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	form := &forms.UpdateRegistryForm{
-		ID: uint(registryID),
-	}
-
-	// decode from JSON to form value
-	if err := json.NewDecoder(r.Body).Decode(form); err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	// validate the form
-	if err := app.validator.Struct(form); err != nil {
-		app.handleErrorFormValidation(err, ErrProjectValidateFields, w)
-		return
-	}
-
-	// convert the form to a registry
-	registry, err := form.ToRegistry(app.Repo.Registry())
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	// handle write to the database
-	registry, err = app.Repo.Registry().UpdateRegistry(registry)
-
-	if err != nil {
-		app.handleErrorDataWrite(err, w)
-		return
-	}
-
-	w.WriteHeader(http.StatusOK)
-
-	regExt := registry.Externalize()
-
-	if err := json.NewEncoder(w).Encode(regExt); err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-}
-
-// HandleDeleteProjectRegistry handles the deletion of a Registry via the registry ID
-func (app *App) HandleDeleteProjectRegistry(w http.ResponseWriter, r *http.Request) {
-	id, err := strconv.ParseUint(chi.URLParam(r, "registry_id"), 0, 64)
-
-	if err != nil || id == 0 {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	reg, err := app.Repo.Registry().ReadRegistry(uint(id))
-
-	if err != nil {
-		app.handleErrorRead(err, ErrProjectDataRead, w)
-		return
-	}
-
-	err = app.Repo.Registry().DeleteRegistry(reg)
-
-	if err != nil {
-		app.handleErrorRead(err, ErrProjectDataRead, w)
-		return
-	}
-
-	w.WriteHeader(http.StatusOK)
-}
-
-// HandleListRepositories returns a list of repositories for a given registry
-func (app *App) HandleListRepositories(w http.ResponseWriter, r *http.Request) {
-	regID, err := strconv.ParseUint(chi.URLParam(r, "registry_id"), 0, 64)
-
-	if err != nil || regID == 0 {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	reg, err := app.Repo.Registry().ReadRegistry(uint(regID))
-
-	if err != nil {
-		app.handleErrorRead(err, ErrProjectDataRead, w)
-		return
-	}
-
-	// cast to a registry from registry package
-	_reg := registry.Registry(*reg)
-	regAPI := &_reg
-
-	repos, err := regAPI.ListRepositories(app.Repo, app.DOConf)
-
-	if err != nil {
-		app.handleErrorRead(err, ErrProjectDataRead, w)
-		return
-	}
-
-	w.WriteHeader(http.StatusOK)
-
-	if err := json.NewEncoder(w).Encode(repos); err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-}
-
-// HandleListImages retrieves a list of repo names
-func (app *App) HandleListImages(w http.ResponseWriter, r *http.Request) {
-	regID, err := strconv.ParseUint(chi.URLParam(r, "registry_id"), 0, 64)
-
-	if err != nil || regID == 0 {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	repoName := chi.URLParam(r, "*")
-
-	reg, err := app.Repo.Registry().ReadRegistry(uint(regID))
-
-	if err != nil {
-		app.handleErrorRead(err, ErrProjectDataRead, w)
-		return
-	}
-
-	// cast to a registry from registry package
-	_reg := registry.Registry(*reg)
-	regAPI := &_reg
-
-	imgs, err := regAPI.ListImages(repoName, app.Repo, app.DOConf)
-
-	if err != nil {
-		app.handleErrorRead(err, ErrProjectDataRead, w)
-		return
-	}
-
-	w.WriteHeader(http.StatusOK)
-
-	if err := json.NewEncoder(w).Encode(imgs); err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-}

+ 0 - 314
server/api/registry_handler_test.go

@@ -1,314 +0,0 @@
-package api_test
-
-// import (
-// 	"encoding/json"
-// 	"net/http"
-// 	"net/http/httptest"
-// 	"strings"
-// 	"testing"
-
-// 	"github.com/go-test/deep"
-// 	"github.com/porter-dev/porter/internal/kubernetes"
-// 	"github.com/porter-dev/porter/internal/models"
-// )
-
-// // ------------------------- TEST TYPES AND MAIN LOOP ------------------------- //
-
-// type regTest struct {
-// 	initializers []func(t *tester)
-// 	msg          string
-// 	method       string
-// 	endpoint     string
-// 	body         string
-// 	expStatus    int
-// 	expBody      string
-// 	useCookie    bool
-// 	validators   []func(c *regTest, tester *tester, t *testing.T)
-// }
-
-// type imagesTest struct {
-// 	initializers []func(tester *tester)
-// 	msg          string
-// 	method       string
-// 	endpoint     string
-// 	body         string
-// 	expStatus    int
-// 	expBody      string
-// 	useCookie    bool
-// 	validators   []func(c *imagesTest, tester *tester, t *testing.T)
-// }
-
-// func testRegistryRequests(t *testing.T, tests []*regTest, canQuery bool) {
-// 	for _, c := range tests {
-// 		// create a new tester
-// 		tester := newTester(canQuery)
-
-// 		// if there's an initializer, call it
-// 		for _, init := range c.initializers {
-// 			init(tester)
-// 		}
-
-// 		req, err := http.NewRequest(
-// 			c.method,
-// 			c.endpoint,
-// 			strings.NewReader(c.body),
-// 		)
-
-// 		tester.req = req
-
-// 		if c.useCookie {
-// 			req.AddCookie(tester.cookie)
-// 		}
-
-// 		if err != nil {
-// 			t.Fatal(err)
-// 		}
-
-// 		tester.execute()
-// 		rr := tester.rr
-
-// 		// first, check that the status matches
-// 		if status := rr.Code; status != c.expStatus {
-// 			t.Errorf("%s, handler returned wrong status code: got %v want %v",
-// 				c.msg, status, c.expStatus)
-// 		}
-
-// 		// if there's a validator, call it
-// 		for _, validate := range c.validators {
-// 			validate(c, tester, t)
-// 		}
-// 	}
-// }
-
-// func testImagesRequests(t *testing.T, tests []*imagesTest, canQuery bool) {
-// 	for _, c := range tests {
-// 		// create a new tester
-// 		tester := newTester(canQuery)
-
-// 		// if there's an initializer, call it
-// 		for _, init := range c.initializers {
-// 			init(tester)
-// 		}
-
-// 		req, err := http.NewRequest(
-// 			c.method,
-// 			c.endpoint,
-// 			strings.NewReader(c.body),
-// 		)
-
-// 		tester.req = req
-
-// 		if c.useCookie {
-// 			req.AddCookie(tester.cookie)
-// 		}
-
-// 		if err != nil {
-// 			t.Fatal(err)
-// 		}
-
-// 		tester.execute()
-// 		rr := tester.rr
-
-// 		// first, check that the status matches
-// 		if status := rr.Code; status != c.expStatus {
-// 			t.Errorf("%s, handler returned wrong status code: got %v want %v",
-// 				c.msg, status, c.expStatus)
-// 		}
-
-// 		// if there's a validator, call it
-// 		for _, validate := range c.validators {
-// 			validate(c, tester, t)
-// 		}
-// 	}
-// }
-
-// // ------------------------- TEST FIXTURES AND FUNCTIONS  ------------------------- //
-
-// // var createRegistryTests = []*regTest{
-// // 	&regTest{
-// // 		initializers: []func(t *tester){
-// // 			initUserDefault,
-// // 			initProject,
-// // 			initAWSIntegration,
-// // 		},
-// // 		msg:       "Create registry",
-// // 		method:    "POST",
-// // 		endpoint:  "/api/projects/1/registries",
-// // 		body:      `{"name":"registry-test","aws_integration_id":1}`,
-// // 		expStatus: http.StatusCreated,
-// // 		expBody:   `{"id":1,"name":"registry-test","project_id":1,"service":"ecr"}`,
-// // 		useCookie: true,
-// // 		validators: []func(c *regTest, tester *tester, t *testing.T){
-// // 			regBodyValidator,
-// // 		},
-// // 	},
-// // }
-
-// // func TestHandleCreateRegistry(t *testing.T) {
-// // 	testRegistryRequests(t, createRegistryTests, true)
-// // }
-
-// var listRegistryTests = []*regTest{
-// 	{
-// 		initializers: []func(t *tester){
-// 			initUserDefault,
-// 			initProject,
-// 			initRegistry,
-// 		},
-// 		msg:       "List registries",
-// 		method:    "GET",
-// 		endpoint:  "/api/projects/1/registries",
-// 		body:      ``,
-// 		expStatus: http.StatusOK,
-// 		expBody:   `[{"id":1,"name":"registry-test","project_id":1,"service":"ecr"}]`,
-// 		useCookie: true,
-// 		validators: []func(c *regTest, tester *tester, t *testing.T){
-// 			regsBodyValidator,
-// 		},
-// 	},
-// }
-
-// func TestHandleListRegistries(t *testing.T) {
-// 	testRegistryRequests(t, listRegistryTests, true)
-// }
-
-// var updateRegistryTests = []*regTest{
-// 	{
-// 		initializers: []func(t *tester){
-// 			initUserDefault,
-// 			initProject,
-// 			initRegistry,
-// 		},
-// 		msg:       "Update registry name",
-// 		method:    "POST",
-// 		endpoint:  "/api/projects/1/registries/1",
-// 		body:      `{"name":"registry-new-name"}`,
-// 		expStatus: http.StatusOK,
-// 		expBody:   `{"id":1,"name":"registry-new-name","project_id":1,"service":"ecr"}`,
-// 		useCookie: true,
-// 		validators: []func(c *regTest, tester *tester, t *testing.T){
-// 			regBodyValidator,
-// 		},
-// 	},
-// }
-
-// func TestHandleUpdateRegistry(t *testing.T) {
-// 	testRegistryRequests(t, updateRegistryTests, true)
-// }
-
-// var deleteRegTests = []*regTest{
-// 	{
-// 		initializers: []func(t *tester){
-// 			initUserDefault,
-// 			initProject,
-// 			initRegistry,
-// 		},
-// 		msg:       "Delete registry",
-// 		method:    "DELETE",
-// 		endpoint:  "/api/projects/1/registries/1",
-// 		body:      ``,
-// 		expStatus: http.StatusOK,
-// 		expBody:   ``,
-// 		useCookie: true,
-// 		validators: []func(c *regTest, tester *tester, t *testing.T){
-// 			func(c *regTest, tester *tester, t *testing.T) {
-// 				req, err := http.NewRequest(
-// 					"GET",
-// 					"/api/projects/1/registries",
-// 					strings.NewReader(""),
-// 				)
-
-// 				req.AddCookie(tester.cookie)
-
-// 				if err != nil {
-// 					t.Fatal(err)
-// 				}
-
-// 				rr2 := httptest.NewRecorder()
-
-// 				tester.router.ServeHTTP(rr2, req)
-
-// 				if status := rr2.Code; status != 200 {
-// 					t.Errorf("DELETE registry validation, handler returned wrong status code: got %v want %v",
-// 						status, 200)
-// 				}
-
-// 				gotBody := make([]*models.RegistryExternal, 0)
-// 				expBody := make([]*models.RegistryExternal, 0)
-
-// 				json.Unmarshal(rr2.Body.Bytes(), &gotBody)
-
-// 				if diff := deep.Equal(gotBody, expBody); diff != nil {
-// 					t.Errorf("handler returned wrong body:\n")
-// 					t.Error(diff)
-// 				}
-// 			},
-// 		},
-// 	},
-// }
-
-// func TestHandleDeleteRegistry(t *testing.T) {
-// 	testRegistryRequests(t, deleteRegTests, true)
-// }
-
-// // ------------------------- INITIALIZERS AND VALIDATORS ------------------------- //
-
-// func initRegistry(tester *tester) {
-// 	proj, _ := tester.repo.Project().ReadProject(1)
-
-// 	reg := &models.Registry{
-// 		Name:             "registry-test",
-// 		ProjectID:        proj.Model.ID,
-// 		AWSIntegrationID: 1,
-// 	}
-
-// 	tester.repo.Registry().CreateRegistry(reg)
-// }
-
-// func regBodyValidator(c *regTest, tester *tester, t *testing.T) {
-// 	gotBody := &models.RegistryExternal{}
-// 	expBody := &models.RegistryExternal{}
-
-// 	json.Unmarshal(tester.rr.Body.Bytes(), &gotBody)
-// 	json.Unmarshal([]byte(c.expBody), &expBody)
-
-// 	if diff := deep.Equal(gotBody, expBody); diff != nil {
-// 		t.Errorf("handler returned wrong body:\n")
-// 		t.Error(diff)
-// 	}
-// }
-
-// func regsBodyValidator(c *regTest, tester *tester, t *testing.T) {
-// 	gotBody := make([]*models.RegistryExternal, 0)
-// 	expBody := make([]*models.RegistryExternal, 0)
-
-// 	json.Unmarshal(tester.rr.Body.Bytes(), &gotBody)
-// 	json.Unmarshal([]byte(c.expBody), &expBody)
-
-// 	if diff := deep.Equal(gotBody, expBody); diff != nil {
-// 		t.Errorf("handler returned wrong body:\n")
-// 		t.Error(diff)
-// 	}
-// }
-
-// func initDefaultImages(tester *tester) {
-// 	initUserDefault(tester)
-
-// 	agent := kubernetes.GetAgentTesting(defaultObjects...)
-
-// 	// overwrite the test agent with new resources
-// 	tester.app.TestAgents.K8sAgent = agent
-// }
-
-// func imagesListValidator(c *imagesTest, tester *tester, t *testing.T) {
-// 	var gotBody map[string]interface{}
-// 	var expBody map[string]interface{}
-
-// 	json.Unmarshal(tester.rr.Body.Bytes(), &gotBody)
-// 	json.Unmarshal([]byte(c.expBody), &expBody)
-
-// 	if diff := deep.Equal(gotBody, expBody); diff != nil {
-// 		t.Errorf("handler returned wrong body:\n")
-// 		t.Error(diff)
-// 	}
-// }

+ 0 - 1844
server/api/release_handler.go

@@ -1,1844 +0,0 @@
-package api
-
-import (
-	"encoding/json"
-	"fmt"
-	"net/http"
-	"net/url"
-	"strconv"
-	"strings"
-	"sync"
-
-	"gorm.io/gorm"
-
-	semver "github.com/Masterminds/semver/v3"
-	"github.com/porter-dev/porter/internal/analytics"
-	"github.com/porter-dev/porter/internal/kubernetes/prometheus"
-	"github.com/porter-dev/porter/internal/models"
-	"github.com/porter-dev/porter/internal/templater/parser"
-	"helm.sh/helm/v3/pkg/chart"
-	"helm.sh/helm/v3/pkg/release"
-	v1 "k8s.io/api/core/v1"
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-
-	"github.com/go-chi/chi"
-	"github.com/porter-dev/porter/internal/forms"
-	"github.com/porter-dev/porter/internal/helm"
-	"github.com/porter-dev/porter/internal/helm/grapher"
-	"github.com/porter-dev/porter/internal/helm/loader"
-	"github.com/porter-dev/porter/internal/integrations/ci/actions"
-	"github.com/porter-dev/porter/internal/integrations/slack"
-	"github.com/porter-dev/porter/internal/kubernetes"
-	"github.com/porter-dev/porter/internal/repository"
-	"gopkg.in/yaml.v2"
-)
-
-// Enumeration of release API error codes, represented as int64
-const (
-	ErrReleaseDecode ErrorCode = iota + 600
-	ErrReleaseValidateFields
-	ErrReleaseReadData
-	ErrReleaseDeploy
-)
-
-var (
-	createEnvSecretConstraint, _ = semver.NewConstraint(" < 0.1.0")
-)
-
-// HandleListReleases retrieves a list of releases for a cluster
-// with various filter options
-func (app *App) HandleListReleases(w http.ResponseWriter, r *http.Request) {
-	form := &forms.ListReleaseForm{
-		ReleaseForm: &forms.ReleaseForm{
-			Form: &helm.Form{
-				Repo:              app.Repo,
-				DigitalOceanOAuth: app.DOConf,
-			},
-		},
-		ListFilter: &helm.ListFilter{},
-	}
-
-	agent, err := app.getAgentFromQueryParams(
-		w,
-		r,
-		form.ReleaseForm,
-		form.ReleaseForm.PopulateHelmOptionsFromQueryParams,
-		form.PopulateListFromQueryParams,
-	)
-
-	// errors are handled in app.getAgentFromQueryParams
-	if err != nil {
-		return
-	}
-
-	releases, err := agent.ListReleases(form.Namespace, form.ListFilter)
-	var releaseList []*release.Release
-	// Clean up unused properties, these values are unnecesary to display the frontend rn
-	for _, r := range releases {
-		r.Chart.Files = []*chart.File{}
-		r.Chart.Templates = []*chart.File{}
-		r.Manifest = ""
-		r.Chart.Values = nil
-		r.Info.Notes = ""
-		releaseList = append(releaseList, r)
-	}
-
-	if err != nil {
-		app.handleErrorRead(err, ErrReleaseReadData, w)
-		return
-	}
-
-	if err := json.NewEncoder(w).Encode(releaseList); err != nil {
-		app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
-		return
-	}
-}
-
-// PorterRelease is a helm release with a form attached
-type PorterRelease struct {
-	*release.Release
-	Form            *models.FormYAML                `json:"form"`
-	HasMetrics      bool                            `json:"has_metrics"`
-	LatestVersion   string                          `json:"latest_version"`
-	GitActionConfig *models.GitActionConfigExternal `json:"git_action_config"`
-	ImageRepoURI    string                          `json:"image_repo_uri"`
-}
-
-// HandleGetRelease retrieves a single release based on a name and revision
-func (app *App) HandleGetRelease(w http.ResponseWriter, r *http.Request) {
-	name := chi.URLParam(r, "name")
-	revision, err := strconv.ParseUint(chi.URLParam(r, "revision"), 0, 64)
-
-	form := &forms.GetReleaseForm{
-		ReleaseForm: &forms.ReleaseForm{
-			Form: &helm.Form{
-				Repo:              app.Repo,
-				DigitalOceanOAuth: app.DOConf,
-			},
-		},
-		Name:     name,
-		Revision: int(revision),
-	}
-
-	agent, err := app.getAgentFromQueryParams(
-		w,
-		r,
-		form.ReleaseForm,
-		form.ReleaseForm.PopulateHelmOptionsFromQueryParams,
-	)
-
-	// errors are handled in app.getAgentFromQueryParams
-	if err != nil {
-		return
-	}
-
-	release, err := agent.GetRelease(form.Name, form.Revision, false)
-
-	if err != nil {
-		app.sendExternalError(err, http.StatusNotFound, HTTPError{
-			Code:   ErrReleaseReadData,
-			Errors: []string{"release not found"},
-		}, w)
-
-		return
-	}
-
-	// get the filter options
-	k8sForm := &forms.K8sForm{
-		OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
-			Repo:              app.Repo,
-			DigitalOceanOAuth: app.DOConf,
-		},
-	}
-
-	vals, err := url.ParseQuery(r.URL.RawQuery)
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
-		return
-	}
-
-	k8sForm.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster())
-	k8sForm.DefaultNamespace = form.ReleaseForm.Namespace
-
-	// validate the form
-	if err := app.validator.Struct(k8sForm); err != nil {
-		app.handleErrorFormValidation(err, ErrK8sValidate, w)
-		return
-	}
-
-	// create a new dynamic client
-	dynClient, err := kubernetes.GetDynamicClientOutOfClusterConfig(k8sForm.OutOfClusterConfig)
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
-		return
-	}
-
-	parserDef := &parser.ClientConfigDefault{
-		DynamicClient: dynClient,
-		HelmChart:     release.Chart,
-		HelmRelease:   release,
-	}
-
-	res := &PorterRelease{release, nil, false, "", nil, ""}
-
-	for _, file := range release.Chart.Files {
-		if strings.Contains(file.Name, "form.yaml") {
-			formYAML, err := parser.FormYAMLFromBytes(parserDef, file.Data, "")
-
-			if err != nil {
-				break
-			}
-
-			res.Form = formYAML
-			break
-		}
-	}
-
-	// if form not populated, detect common charts
-	if res.Form == nil {
-		// for now just case by name
-		if res.Release.Chart.Name() == "velero" {
-			formYAML, err := parser.FormYAMLFromBytes(parserDef, []byte(veleroForm), "")
-
-			if err == nil {
-				res.Form = formYAML
-			}
-		}
-	}
-
-	// get prometheus service
-	_, found, err := prometheus.GetPrometheusService(agent.K8sAgent.Clientset)
-
-	if err != nil {
-		app.handleErrorFormValidation(err, ErrK8sValidate, w)
-		return
-	}
-
-	res.HasMetrics = found
-
-	// detect if Porter application chart and attempt to get the latest version
-	// from chart repo
-	chartRepoURL, firstFound := app.ChartLookupURLs[res.Chart.Metadata.Name]
-
-	if !firstFound {
-		app.updateChartRepoURLs()
-
-		chartRepoURL, _ = app.ChartLookupURLs[res.Chart.Metadata.Name]
-	}
-
-	if chartRepoURL != "" {
-		repoIndex, err := loader.LoadRepoIndexPublic(chartRepoURL)
-
-		if err == nil {
-			porterChart := loader.FindPorterChartInIndexList(repoIndex, res.Chart.Metadata.Name)
-			res.LatestVersion = res.Chart.Metadata.Version
-
-			// set latest version to the greater of porterChart.Versions and res.Chart.Metadata.Version
-			porterChartVersion, porterChartErr := semver.NewVersion(porterChart.Versions[0])
-			currChartVersion, currChartErr := semver.NewVersion(res.Chart.Metadata.Version)
-
-			if currChartErr == nil && porterChartErr == nil && porterChartVersion.GreaterThan(currChartVersion) {
-				res.LatestVersion = porterChart.Versions[0]
-			}
-		}
-	}
-
-	// if the release was created from this server,
-	modelRelease, err := app.Repo.Release().ReadRelease(form.Cluster.ID, release.Name, release.Namespace)
-
-	if modelRelease != nil {
-		res.ImageRepoURI = modelRelease.ImageRepoURI
-
-		gitAction := modelRelease.GitActionConfig
-
-		if gitAction.ID != 0 {
-			res.GitActionConfig = gitAction.Externalize()
-		}
-	}
-
-	if err := json.NewEncoder(w).Encode(res); err != nil {
-		app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
-		return
-	}
-}
-
-// HandleGetReleaseComponents retrieves kubernetes objects listed in a release identified by name and revision
-func (app *App) HandleGetReleaseComponents(w http.ResponseWriter, r *http.Request) {
-	name := chi.URLParam(r, "name")
-	revision, err := strconv.ParseUint(chi.URLParam(r, "revision"), 0, 64)
-
-	form := &forms.GetReleaseForm{
-		ReleaseForm: &forms.ReleaseForm{
-			Form: &helm.Form{
-				Repo:              app.Repo,
-				DigitalOceanOAuth: app.DOConf,
-			},
-		},
-		Name:     name,
-		Revision: int(revision),
-	}
-
-	agent, err := app.getAgentFromQueryParams(
-		w,
-		r,
-		form.ReleaseForm,
-		form.ReleaseForm.PopulateHelmOptionsFromQueryParams,
-	)
-
-	// errors are handled in app.getAgentFromQueryParams
-	if err != nil {
-		return
-	}
-
-	release, err := agent.GetRelease(form.Name, form.Revision, false)
-
-	if err != nil {
-		app.sendExternalError(err, http.StatusNotFound, HTTPError{
-			Code:   ErrReleaseReadData,
-			Errors: []string{"release not found"},
-		}, w)
-
-		return
-	}
-
-	yamlArr := grapher.ImportMultiDocYAML([]byte(release.Manifest))
-	objects := grapher.ParseObjs(yamlArr, release.Namespace)
-
-	parsed := grapher.ParsedObjs{
-		Objects: objects,
-	}
-
-	parsed.GetControlRel()
-	parsed.GetLabelRel()
-	parsed.GetSpecRel()
-
-	if err := json.NewEncoder(w).Encode(parsed); err != nil {
-		app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
-		return
-	}
-}
-
-// HandleGetReleaseControllers retrieves controllers that belong to a release.
-// Used to display status of charts.
-func (app *App) HandleGetReleaseControllers(w http.ResponseWriter, r *http.Request) {
-	name := chi.URLParam(r, "name")
-	revision, err := strconv.ParseUint(chi.URLParam(r, "revision"), 0, 64)
-
-	form := &forms.GetReleaseForm{
-		ReleaseForm: &forms.ReleaseForm{
-			Form: &helm.Form{
-				Repo:              app.Repo,
-				DigitalOceanOAuth: app.DOConf,
-			},
-		},
-		Name:     name,
-		Revision: int(revision),
-	}
-
-	agent, err := app.getAgentFromQueryParams(
-		w,
-		r,
-		form.ReleaseForm,
-		form.ReleaseForm.PopulateHelmOptionsFromQueryParams,
-	)
-
-	// errors are handled in app.getAgentFromQueryParams
-	if err != nil {
-		return
-	}
-
-	release, err := agent.GetRelease(form.Name, form.Revision, false)
-
-	if err != nil {
-		app.sendExternalError(err, http.StatusNotFound, HTTPError{
-			Code:   ErrReleaseReadData,
-			Errors: []string{"release not found"},
-		}, w)
-
-		return
-	}
-
-	vals, err := url.ParseQuery(r.URL.RawQuery)
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
-		return
-	}
-
-	// get the filter options
-	k8sForm := &forms.K8sForm{
-		OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
-			Repo:              app.Repo,
-			DigitalOceanOAuth: app.DOConf,
-		},
-	}
-
-	k8sForm.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster())
-	k8sForm.DefaultNamespace = form.ReleaseForm.Namespace
-
-	// validate the form
-	if err := app.validator.Struct(k8sForm); err != nil {
-		app.handleErrorFormValidation(err, ErrK8sValidate, w)
-		return
-	}
-
-	// create a new kubernetes agent
-	var k8sAgent *kubernetes.Agent
-
-	if app.ServerConf.IsTesting {
-		k8sAgent = app.TestAgents.K8sAgent
-	} else {
-		k8sAgent, err = kubernetes.GetAgentOutOfClusterConfig(k8sForm.OutOfClusterConfig)
-	}
-
-	yamlArr := grapher.ImportMultiDocYAML([]byte(release.Manifest))
-	controllers := grapher.ParseControllers(yamlArr)
-	retrievedControllers := []interface{}{}
-
-	// get current status of each controller
-	// TODO: refactor with type assertion
-	for _, c := range controllers {
-		c.Namespace = form.ReleaseForm.Form.Namespace
-		switch c.Kind {
-		case "Deployment":
-			rc, err := k8sAgent.GetDeployment(c)
-
-			if err != nil {
-				app.handleErrorDataRead(err, w)
-				return
-			}
-
-			rc.Kind = c.Kind
-			retrievedControllers = append(retrievedControllers, rc)
-		case "StatefulSet":
-			rc, err := k8sAgent.GetStatefulSet(c)
-
-			if err != nil {
-				app.handleErrorDataRead(err, w)
-				return
-			}
-
-			rc.Kind = c.Kind
-			retrievedControllers = append(retrievedControllers, rc)
-		case "DaemonSet":
-			rc, err := k8sAgent.GetDaemonSet(c)
-
-			if err != nil {
-				app.handleErrorDataRead(err, w)
-				return
-			}
-
-			rc.Kind = c.Kind
-			retrievedControllers = append(retrievedControllers, rc)
-		case "ReplicaSet":
-			rc, err := k8sAgent.GetReplicaSet(c)
-
-			if err != nil {
-				app.handleErrorDataRead(err, w)
-				return
-			}
-
-			rc.Kind = c.Kind
-			retrievedControllers = append(retrievedControllers, rc)
-		case "CronJob":
-			rc, err := k8sAgent.GetCronJob(c)
-
-			if err != nil {
-				app.handleErrorDataRead(err, w)
-				return
-			}
-
-			rc.Kind = c.Kind
-			retrievedControllers = append(retrievedControllers, rc)
-		}
-	}
-
-	if err := json.NewEncoder(w).Encode(retrievedControllers); err != nil {
-		app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
-		return
-	}
-}
-
-// HandleGetReleaseAllPods retrieves all pods that are associated with a given release.
-func (app *App) HandleGetReleaseAllPods(w http.ResponseWriter, r *http.Request) {
-	name := chi.URLParam(r, "name")
-	revision, err := strconv.ParseUint(chi.URLParam(r, "revision"), 0, 64)
-
-	form := &forms.GetReleaseForm{
-		ReleaseForm: &forms.ReleaseForm{
-			Form: &helm.Form{
-				Repo:              app.Repo,
-				DigitalOceanOAuth: app.DOConf,
-			},
-		},
-		Name:     name,
-		Revision: int(revision),
-	}
-
-	agent, err := app.getAgentFromQueryParams(
-		w,
-		r,
-		form.ReleaseForm,
-		form.ReleaseForm.PopulateHelmOptionsFromQueryParams,
-	)
-
-	// errors are handled in app.getAgentFromQueryParams
-	if err != nil {
-		return
-	}
-
-	release, err := agent.GetRelease(form.Name, form.Revision, false)
-
-	if err != nil {
-		app.sendExternalError(err, http.StatusNotFound, HTTPError{
-			Code:   ErrReleaseReadData,
-			Errors: []string{"release not found"},
-		}, w)
-
-		return
-	}
-
-	vals, err := url.ParseQuery(r.URL.RawQuery)
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
-		return
-	}
-
-	// get the filter options
-	k8sForm := &forms.K8sForm{
-		OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
-			Repo:              app.Repo,
-			DigitalOceanOAuth: app.DOConf,
-		},
-	}
-
-	k8sForm.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster())
-	k8sForm.DefaultNamespace = form.ReleaseForm.Namespace
-
-	// validate the form
-	if err := app.validator.Struct(k8sForm); err != nil {
-		app.handleErrorFormValidation(err, ErrK8sValidate, w)
-		return
-	}
-
-	// create a new kubernetes agent
-	var k8sAgent *kubernetes.Agent
-
-	if app.ServerConf.IsTesting {
-		k8sAgent = app.TestAgents.K8sAgent
-	} else {
-		k8sAgent, err = kubernetes.GetAgentOutOfClusterConfig(k8sForm.OutOfClusterConfig)
-	}
-
-	yamlArr := grapher.ImportMultiDocYAML([]byte(release.Manifest))
-	controllers := grapher.ParseControllers(yamlArr)
-	pods := make([]v1.Pod, 0)
-
-	// get current status of each controller
-	for _, c := range controllers {
-		var selector *metav1.LabelSelector
-
-		switch c.Kind {
-		case "Deployment":
-			rc, err := k8sAgent.GetDeployment(c)
-
-			if err != nil {
-				app.handleErrorDataRead(err, w)
-				return
-			}
-
-			selector = rc.Spec.Selector
-		case "StatefulSet":
-			rc, err := k8sAgent.GetStatefulSet(c)
-
-			if err != nil {
-				app.handleErrorDataRead(err, w)
-				return
-			}
-
-			selector = rc.Spec.Selector
-		case "DaemonSet":
-			rc, err := k8sAgent.GetDaemonSet(c)
-
-			if err != nil {
-				app.handleErrorDataRead(err, w)
-				return
-			}
-
-			selector = rc.Spec.Selector
-		case "ReplicaSet":
-			rc, err := k8sAgent.GetReplicaSet(c)
-
-			if err != nil {
-				app.handleErrorDataRead(err, w)
-				return
-			}
-
-			selector = rc.Spec.Selector
-		case "CronJob":
-			rc, err := k8sAgent.GetCronJob(c)
-
-			if err != nil {
-				app.handleErrorDataRead(err, w)
-				return
-			}
-
-			selector = rc.Spec.JobTemplate.Spec.Selector
-		}
-
-		selectors := make([]string, 0)
-
-		for key, val := range selector.MatchLabels {
-			selectors = append(selectors, key+"="+val)
-		}
-
-		namespace := vals.Get("namespace")
-		podList, err := k8sAgent.GetPodsByLabel(strings.Join(selectors, ","), namespace)
-
-		if err != nil {
-			app.handleErrorDataRead(err, w)
-			return
-		}
-
-		pods = append(pods, podList.Items...)
-	}
-
-	if err := json.NewEncoder(w).Encode(pods); err != nil {
-		app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
-		return
-	}
-}
-
-type GetJobStatusResult struct {
-	Status    string       `json:"status,omitempty"`
-	StartTime *metav1.Time `json:"start_time,omitempty"`
-}
-
-// HandleGetJobStatus gets the status for a specific job
-func (app *App) HandleGetJobStatus(w http.ResponseWriter, r *http.Request) {
-	name := chi.URLParam(r, "name")
-	namespace := chi.URLParam(r, "namespace")
-
-	form := &forms.GetReleaseForm{
-		ReleaseForm: &forms.ReleaseForm{
-			Form: &helm.Form{
-				Repo:              app.Repo,
-				DigitalOceanOAuth: app.DOConf,
-				Storage:           "secret",
-				Namespace:         namespace,
-			},
-		},
-		Name:     name,
-		Revision: 0,
-	}
-
-	agent, err := app.getAgentFromQueryParams(
-		w,
-		r,
-		form.ReleaseForm,
-		form.ReleaseForm.PopulateHelmOptionsFromQueryParams,
-	)
-
-	// errors are handled in app.getAgentFromQueryParams
-	if err != nil {
-		return
-	}
-
-	release, err := agent.GetRelease(form.Name, form.Revision, false)
-
-	if err != nil {
-		app.sendExternalError(err, http.StatusNotFound, HTTPError{
-			Code:   ErrReleaseReadData,
-			Errors: []string{"release not found"},
-		}, w)
-
-		return
-	}
-
-	vals, err := url.ParseQuery(r.URL.RawQuery)
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
-		return
-	}
-
-	// get the filter options
-	k8sForm := &forms.K8sForm{
-		OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
-			Repo:              app.Repo,
-			DigitalOceanOAuth: app.DOConf,
-		},
-	}
-
-	k8sForm.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster())
-	k8sForm.DefaultNamespace = form.ReleaseForm.Namespace
-
-	// validate the form
-	if err := app.validator.Struct(k8sForm); err != nil {
-		app.handleErrorFormValidation(err, ErrK8sValidate, w)
-		return
-	}
-
-	// create a new kubernetes agent
-	var k8sAgent *kubernetes.Agent
-
-	if app.ServerConf.IsTesting {
-		k8sAgent = app.TestAgents.K8sAgent
-	} else {
-		k8sAgent, err = kubernetes.GetAgentOutOfClusterConfig(k8sForm.OutOfClusterConfig)
-	}
-
-	jobs, err := k8sAgent.ListJobsByLabel(namespace, kubernetes.Label{
-		Key: "helm.sh/chart",
-		Val: fmt.Sprintf("%s-%s", release.Chart.Name(), release.Chart.Metadata.Version),
-	}, kubernetes.Label{
-		Key: "meta.helm.sh/release-name",
-		Val: name,
-	})
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
-		return
-	}
-
-	res := &GetJobStatusResult{}
-
-	// get the most recent job
-	if len(jobs) > 0 {
-		mostRecentJob := jobs[0]
-
-		for _, job := range jobs {
-			createdAt := job.ObjectMeta.CreationTimestamp
-
-			if mostRecentJob.CreationTimestamp.Before(&createdAt) {
-				mostRecentJob = job
-			}
-		}
-
-		res.StartTime = mostRecentJob.Status.StartTime
-
-		// get the status of the most recent job
-		if mostRecentJob.Status.Succeeded >= 1 {
-			res.Status = "succeeded"
-		} else if mostRecentJob.Status.Active >= 1 {
-			res.Status = "running"
-		} else if mostRecentJob.Status.Failed >= 1 {
-			res.Status = "failed"
-		}
-	}
-
-	if err := json.NewEncoder(w).Encode(res); err != nil {
-		app.handleErrorFormDecoding(err, ErrK8sDecode, w)
-		return
-	}
-}
-
-// HandleListReleaseHistory retrieves a history of releases based on a release name
-func (app *App) HandleListReleaseHistory(w http.ResponseWriter, r *http.Request) {
-	name := chi.URLParam(r, "name")
-
-	form := &forms.ListReleaseHistoryForm{
-		ReleaseForm: &forms.ReleaseForm{
-			Form: &helm.Form{
-				Repo:              app.Repo,
-				DigitalOceanOAuth: app.DOConf,
-			},
-		},
-		Name: name,
-	}
-
-	agent, err := app.getAgentFromQueryParams(
-		w,
-		r,
-		form.ReleaseForm,
-		form.ReleaseForm.PopulateHelmOptionsFromQueryParams,
-	)
-
-	// errors are handled in app.getAgentFromQueryParams
-	if err != nil {
-		return
-	}
-
-	release, err := agent.GetReleaseHistory(form.Name)
-
-	if err != nil {
-		app.sendExternalError(err, http.StatusNotFound, HTTPError{
-			Code:   ErrReleaseReadData,
-			Errors: []string{"release not found"},
-		}, w)
-
-		return
-	}
-
-	if err := json.NewEncoder(w).Encode(release); err != nil {
-		app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
-		return
-	}
-}
-
-// HandleGetReleaseToken retrieves the webhook token of a specific release.
-func (app *App) HandleGetReleaseToken(w http.ResponseWriter, r *http.Request) {
-	name := chi.URLParam(r, "name")
-
-	vals, err := url.ParseQuery(r.URL.RawQuery)
-	namespace := vals["namespace"][0]
-
-	clusterID, err := strconv.ParseUint(vals["cluster_id"][0], 10, 64)
-
-	if err != nil {
-		app.sendExternalError(err, http.StatusInternalServerError, HTTPError{
-			Code:   ErrReleaseReadData,
-			Errors: []string{"release not found"},
-		}, w)
-
-		return
-	}
-
-	release, err := app.Repo.Release().ReadRelease(uint(clusterID), name, namespace)
-
-	if err != nil {
-		app.sendExternalError(err, http.StatusInternalServerError, HTTPError{
-			Code:   ErrReleaseReadData,
-			Errors: []string{"release not found"},
-		}, w)
-
-		return
-	}
-
-	releaseExt := release.Externalize()
-
-	if err := json.NewEncoder(w).Encode(releaseExt); err != nil {
-		app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
-		return
-	}
-}
-
-// HandleCreateWebhookToken creates a new webhook token for a release
-func (app *App) HandleCreateWebhookToken(w http.ResponseWriter, r *http.Request) {
-	name := chi.URLParam(r, "name")
-
-	vals, err := url.ParseQuery(r.URL.RawQuery)
-
-	if err != nil {
-		app.sendExternalError(err, http.StatusInternalServerError, HTTPError{
-			Code:   ErrReleaseReadData,
-			Errors: []string{"release not found"},
-		}, w)
-
-		return
-	}
-
-	// read the release from the target cluster
-	form := &forms.ReleaseForm{
-		Form: &helm.Form{
-			Repo:              app.Repo,
-			DigitalOceanOAuth: app.DOConf,
-		},
-	}
-
-	form.PopulateHelmOptionsFromQueryParams(
-		vals,
-		app.Repo.Cluster(),
-	)
-
-	agent, err := app.getAgentFromReleaseForm(
-		w,
-		r,
-		form,
-	)
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrUserDecode, w)
-		return
-	}
-
-	rel, err := agent.GetRelease(name, 0, false)
-
-	if err != nil {
-		app.handleErrorDataRead(err, w)
-		return
-	}
-
-	token, err := repository.GenerateRandomBytes(16)
-
-	if err != nil {
-		app.handleErrorInternal(err, w)
-		return
-	}
-
-	// create release with webhook token in db
-	image, ok := rel.Config["image"].(map[string]interface{})
-	if !ok {
-		app.handleErrorInternal(fmt.Errorf("Could not find field image in config"), w)
-		return
-	}
-
-	repository := image["repository"]
-	repoStr, ok := repository.(string)
-
-	if !ok {
-		app.handleErrorInternal(fmt.Errorf("Could not find field repository in config"), w)
-		return
-	}
-
-	release := &models.Release{
-		ClusterID:    form.Form.Cluster.ID,
-		ProjectID:    form.Form.Cluster.ProjectID,
-		Namespace:    form.Form.Namespace,
-		Name:         name,
-		WebhookToken: token,
-		ImageRepoURI: repoStr,
-	}
-
-	release, err = app.Repo.Release().CreateRelease(release)
-
-	if err != nil {
-		app.handleErrorInternal(err, w)
-		return
-	}
-
-	releaseExt := release.Externalize()
-
-	if err := json.NewEncoder(w).Encode(releaseExt); err != nil {
-		app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
-		return
-	}
-}
-
-type ContainerEnvConfig struct {
-	Container struct {
-		Env struct {
-			Normal map[string]string `yaml:"normal"`
-		} `yaml:"env"`
-	} `yaml:"container"`
-}
-
-// HandleUpgradeRelease upgrades a release with new values.yaml
-func (app *App) HandleUpgradeRelease(w http.ResponseWriter, r *http.Request) {
-	projID, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
-
-	if err != nil || projID == 0 {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	name := chi.URLParam(r, "name")
-
-	vals, err := url.ParseQuery(r.URL.RawQuery)
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
-		return
-	}
-
-	form := &forms.UpgradeReleaseForm{
-		ReleaseForm: &forms.ReleaseForm{
-			Form: &helm.Form{
-				Repo:              app.Repo,
-				DigitalOceanOAuth: app.DOConf,
-			},
-		},
-		Name: name,
-	}
-
-	form.ReleaseForm.PopulateHelmOptionsFromQueryParams(
-		vals,
-		app.Repo.Cluster(),
-	)
-
-	if err := json.NewDecoder(r.Body).Decode(form); err != nil {
-		app.handleErrorFormDecoding(err, ErrUserDecode, w)
-		return
-	}
-
-	agent, err := app.getAgentFromReleaseForm(
-		w,
-		r,
-		form.ReleaseForm,
-	)
-
-	// errors are handled in app.getAgentFromBodyParams
-	if err != nil {
-		return
-	}
-
-	registries, err := app.Repo.Registry().ListRegistriesByProjectID(uint(projID))
-
-	if err != nil {
-		app.handleErrorDataRead(err, w)
-		return
-	}
-
-	conf := &helm.UpgradeReleaseConfig{
-		Name:       form.Name,
-		Cluster:    form.ReleaseForm.Cluster,
-		Repo:       app.Repo,
-		Registries: registries,
-	}
-
-	// if the chart version is set, load a chart from the repo
-	if form.ChartVersion != "" {
-		release, err := agent.GetRelease(form.Name, 0, false)
-
-		if err != nil {
-			app.sendExternalError(err, http.StatusNotFound, HTTPError{
-				Code:   ErrReleaseReadData,
-				Errors: []string{"chart version not found"},
-			}, w)
-
-			return
-		}
-
-		chartRepoURL, foundFirst := app.ChartLookupURLs[release.Chart.Metadata.Name]
-
-		if !foundFirst {
-			app.updateChartRepoURLs()
-
-			var found bool
-
-			chartRepoURL, found = app.ChartLookupURLs[release.Chart.Metadata.Name]
-
-			if !found {
-				app.sendExternalError(err, http.StatusNotFound, HTTPError{
-					Code:   ErrReleaseReadData,
-					Errors: []string{"chart not found"},
-				}, w)
-
-				return
-			}
-		}
-
-		chart, err := loader.LoadChartPublic(
-			chartRepoURL,
-			release.Chart.Metadata.Name,
-			form.ChartVersion,
-		)
-
-		if err != nil {
-			app.sendExternalError(err, http.StatusNotFound, HTTPError{
-				Code:   ErrReleaseReadData,
-				Errors: []string{"chart not found"},
-			}, w)
-
-			return
-		}
-
-		conf.Chart = chart
-	}
-
-	rel, upgradeErr := agent.UpgradeRelease(conf, form.Values, app.DOConf)
-
-	slackInts, _ := app.Repo.SlackIntegration().ListSlackIntegrationsByProjectID(uint(projID))
-
-	clusterID, err := strconv.ParseUint(vals["cluster_id"][0], 10, 64)
-	release, _ := app.Repo.Release().ReadRelease(uint(clusterID), name, form.Namespace)
-
-	var notifConf *models.NotificationConfigExternal
-	notifConf = nil
-	if release != nil && release.NotificationConfig != 0 {
-		conf, err := app.Repo.NotificationConfig().ReadNotificationConfig(release.NotificationConfig)
-
-		if err != nil {
-			app.handleErrorInternal(err, w)
-			return
-		}
-
-		notifConf = conf.Externalize()
-	}
-
-	notifier := slack.NewSlackNotifier(notifConf, slackInts...)
-
-	notifyOpts := &slack.NotifyOpts{
-		ProjectID:   uint(projID),
-		ClusterID:   form.Cluster.ID,
-		ClusterName: form.Cluster.Name,
-		Name:        name,
-		Namespace:   form.Namespace,
-		URL: fmt.Sprintf(
-			"%s/applications/%s/%s/%s",
-			app.ServerConf.ServerURL,
-			url.PathEscape(form.Cluster.Name),
-			form.Namespace,
-			name,
-		) + fmt.Sprintf("?project_id=%d", uint(projID)),
-	}
-
-	if upgradeErr != nil {
-		notifyOpts.Status = slack.StatusFailed
-		notifyOpts.Info = upgradeErr.Error()
-
-		notifier.Notify(notifyOpts)
-
-		app.sendExternalError(err, http.StatusInternalServerError, HTTPError{
-			Code:   ErrReleaseDeploy,
-			Errors: []string{upgradeErr.Error()},
-		}, w)
-
-		return
-	}
-
-	notifyOpts.Status = string(rel.Info.Status)
-	notifyOpts.Version = rel.Version
-
-	notifier.Notify(notifyOpts)
-
-	// update the github actions env if the release exists and is built from source
-	if cName := rel.Chart.Metadata.Name; cName == "job" || cName == "web" || cName == "worker" {
-		if err != nil {
-			app.sendExternalError(err, http.StatusInternalServerError, HTTPError{
-				Code:   ErrReleaseReadData,
-				Errors: []string{"release not found"},
-			}, w)
-
-			return
-		}
-
-		if release != nil {
-			// update image repo uri if changed
-			repository := rel.Config["image"].(map[string]interface{})["repository"]
-			repoStr, ok := repository.(string)
-
-			if !ok {
-				app.handleErrorInternal(fmt.Errorf("Could not find field repository in config"), w)
-				return
-			}
-
-			if repoStr != release.ImageRepoURI {
-				release, err = app.Repo.Release().UpdateRelease(release)
-
-				if err != nil {
-					app.handleErrorInternal(err, w)
-					return
-				}
-			}
-
-			gitAction := release.GitActionConfig
-
-			if gitAction.ID != 0 {
-				// parse env into build env
-				cEnv := &ContainerEnvConfig{}
-
-				yaml.Unmarshal([]byte(form.Values), cEnv)
-
-				gr, err := app.Repo.GitRepo().ReadGitRepo(gitAction.GitRepoID)
-
-				if err != nil {
-					if err != gorm.ErrRecordNotFound {
-						app.handleErrorInternal(err, w)
-						return
-					}
-					gr = nil
-				}
-
-				repoSplit := strings.Split(gitAction.GitRepo, "/")
-
-				gaRunner := &actions.GithubActions{
-					ServerURL:              app.ServerConf.ServerURL,
-					GithubOAuthIntegration: gr,
-					GithubInstallationID:   gitAction.GithubInstallationID,
-					GithubAppID:            app.GithubAppConf.AppID,
-					GithubAppSecretPath:    app.GithubAppConf.SecretPath,
-					GitRepoName:            repoSplit[1],
-					GitRepoOwner:           repoSplit[0],
-					Repo:                   app.Repo,
-					GithubConf:             app.GithubProjectConf,
-					ProjectID:              uint(projID),
-					ReleaseName:            name,
-					ReleaseNamespace:       release.Namespace,
-					GitBranch:              gitAction.GitBranch,
-					DockerFilePath:         gitAction.DockerfilePath,
-					FolderPath:             gitAction.FolderPath,
-					ImageRepoURL:           gitAction.ImageRepoURI,
-					BuildEnv:               cEnv.Container.Env.Normal,
-					ClusterID:              release.ClusterID,
-					Version:                gitAction.Version,
-				}
-
-				actionVersion, err := semver.NewVersion(gaRunner.Version)
-				if err != nil {
-					app.handleErrorInternal(err, w)
-				}
-
-				if createEnvSecretConstraint.Check(actionVersion) {
-					if err := gaRunner.CreateEnvSecret(); err != nil {
-						app.sendExternalError(err, http.StatusInternalServerError, HTTPError{
-							Code:   ErrReleaseReadData,
-							Errors: []string{"could not update github secret"},
-						}, w)
-					}
-				}
-			}
-		}
-	}
-
-	w.WriteHeader(http.StatusOK)
-}
-
-// HandleReleaseDeployWebhook upgrades a release when a chart specific webhook is called.
-func (app *App) HandleReleaseDeployWebhook(w http.ResponseWriter, r *http.Request) {
-	token := chi.URLParam(r, "token")
-
-	// retrieve release by token
-	release, err := app.Repo.Release().ReadReleaseByWebhookToken(token)
-
-	if err != nil {
-		app.sendExternalError(err, http.StatusInternalServerError, HTTPError{
-			Code:   ErrReleaseReadData,
-			Errors: []string{"release not found with given webhook"},
-		}, w)
-
-		return
-	}
-
-	params := map[string][]string{}
-	params["cluster_id"] = []string{fmt.Sprint(release.ClusterID)}
-	params["storage"] = []string{"secret"}
-	params["namespace"] = []string{release.Namespace}
-
-	vals, err := url.ParseQuery(r.URL.RawQuery)
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
-		return
-	}
-
-	form := &forms.UpgradeReleaseForm{
-		ReleaseForm: &forms.ReleaseForm{
-			Form: &helm.Form{
-				Repo:              app.Repo,
-				DigitalOceanOAuth: app.DOConf,
-			},
-		},
-		Name: release.Name,
-	}
-
-	form.ReleaseForm.PopulateHelmOptionsFromQueryParams(
-		params,
-		app.Repo.Cluster(),
-	)
-
-	agent, err := app.getAgentFromReleaseForm(
-		w,
-		r,
-		form.ReleaseForm,
-	)
-
-	// errors are handled in app.getAgentFromBodyParams
-	if err != nil {
-		return
-	}
-
-	rel, err := agent.GetRelease(form.Name, 0, false)
-
-	// repository is set to current repository by default
-	commit := vals["commit"][0]
-	repository := rel.Config["image"].(map[string]interface{})["repository"]
-
-	gitAction := release.GitActionConfig
-
-	if gitAction.ID != 0 && (repository == "porterdev/hello-porter" || repository == "public.ecr.aws/o1j4x7p4/hello-porter") {
-		repository = gitAction.ImageRepoURI
-	} else if gitAction.ID != 0 && (repository == "porterdev/hello-porter-job" || repository == "public.ecr.aws/o1j4x7p4/hello-porter-job") {
-		repository = gitAction.ImageRepoURI
-	}
-
-	image := map[string]interface{}{}
-	image["repository"] = repository
-	image["tag"] = commit
-	rel.Config["image"] = image
-
-	if rel.Config["auto_deploy"] == false {
-		app.sendExternalError(err, http.StatusInternalServerError, HTTPError{
-			Code:   ErrReleaseDeploy,
-			Errors: []string{"Deploy webhook is disabled for this deployment."},
-		}, w)
-
-		return
-	}
-
-	registries, err := app.Repo.Registry().ListRegistriesByProjectID(uint(form.ReleaseForm.Cluster.ProjectID))
-
-	if err != nil {
-		app.handleErrorDataRead(err, w)
-		return
-	}
-
-	conf := &helm.UpgradeReleaseConfig{
-		Name:       form.Name,
-		Cluster:    form.ReleaseForm.Cluster,
-		Repo:       app.Repo,
-		Registries: registries,
-		Values:     rel.Config,
-	}
-
-	slackInts, _ := app.Repo.SlackIntegration().ListSlackIntegrationsByProjectID(uint(form.ReleaseForm.Cluster.ProjectID))
-
-	var notifConf *models.NotificationConfigExternal
-	notifConf = nil
-	if release != nil && release.NotificationConfig != 0 {
-		conf, err := app.Repo.NotificationConfig().ReadNotificationConfig(release.NotificationConfig)
-
-		if err != nil {
-			app.handleErrorInternal(err, w)
-			return
-		}
-
-		notifConf = conf.Externalize()
-	}
-
-	notifier := slack.NewSlackNotifier(notifConf, slackInts...)
-
-	notifyOpts := &slack.NotifyOpts{
-		ProjectID:   uint(form.ReleaseForm.Cluster.ProjectID),
-		ClusterID:   form.Cluster.ID,
-		ClusterName: form.Cluster.Name,
-		Name:        rel.Name,
-		Namespace:   rel.Namespace,
-		URL: fmt.Sprintf(
-			"%s/applications/%s/%s/%s",
-			app.ServerConf.ServerURL,
-			url.PathEscape(form.Cluster.Name),
-			form.Namespace,
-			rel.Name,
-		) + fmt.Sprintf("?project_id=%d", uint(form.ReleaseForm.Cluster.ProjectID)),
-	}
-
-	rel, err = agent.UpgradeReleaseByValues(conf, app.DOConf)
-
-	if err != nil {
-		notifyOpts.Status = slack.StatusFailed
-		notifyOpts.Info = err.Error()
-
-		notifier.Notify(notifyOpts)
-
-		app.sendExternalError(err, http.StatusInternalServerError, HTTPError{
-			Code:   ErrReleaseDeploy,
-			Errors: []string{err.Error()},
-		}, w)
-
-		return
-	}
-
-	notifyOpts.Status = string(rel.Info.Status)
-	notifyOpts.Version = rel.Version
-
-	notifier.Notify(notifyOpts)
-
-	userID, _ := app.getUserIDFromRequest(r)
-
-	app.AnalyticsClient.Track(analytics.ApplicationDeploymentWebhookTrack(&analytics.ApplicationDeploymentWebhookTrackOpts{
-		ImageURI: fmt.Sprintf("%v", repository),
-		ApplicationScopedTrackOpts: analytics.GetApplicationScopedTrackOpts(
-			userID,
-			release.ProjectID,
-			release.ClusterID,
-			release.Name,
-			release.Namespace,
-			rel.Chart.Metadata.Name,
-		),
-	}))
-
-	w.WriteHeader(http.StatusOK)
-}
-
-// HandleReleaseJobUpdateImage
-func (app *App) HandleReleaseUpdateJobImages(w http.ResponseWriter, r *http.Request) {
-	vals, err := url.ParseQuery(r.URL.RawQuery)
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
-		return
-	}
-
-	form := &forms.UpdateImageForm{
-		ReleaseForm: &forms.ReleaseForm{
-			Form: &helm.Form{
-				Repo:              app.Repo,
-				DigitalOceanOAuth: app.DOConf,
-			},
-		},
-	}
-
-	form.ReleaseForm.PopulateHelmOptionsFromQueryParams(
-		vals,
-		app.Repo.Cluster(),
-	)
-
-	if err := json.NewDecoder(r.Body).Decode(form); err != nil {
-		app.handleErrorFormDecoding(err, ErrUserDecode, w)
-		return
-	}
-
-	releases, err := app.Repo.Release().ListReleasesByImageRepoURI(form.Cluster.ID, form.ImageRepoURI)
-
-	if err != nil {
-		app.sendExternalError(err, http.StatusInternalServerError, HTTPError{
-			Code:   ErrReleaseReadData,
-			Errors: []string{"releases not found with given image repo uri"},
-		}, w)
-
-		return
-	}
-
-	agent, err := app.getAgentFromReleaseForm(
-		w,
-		r,
-		form.ReleaseForm,
-	)
-
-	// errors are handled in app.getAgentFromBodyParams
-	if err != nil {
-		return
-	}
-
-	registries, err := app.Repo.Registry().ListRegistriesByProjectID(uint(form.ReleaseForm.Cluster.ProjectID))
-
-	if err != nil {
-		app.handleErrorDataRead(err, w)
-		return
-	}
-
-	// asynchronously update releases with that image repo uri
-	var wg sync.WaitGroup
-	mu := &sync.Mutex{}
-	errors := make([]string, 0)
-
-	for i := range releases {
-		index := i
-		wg.Add(1)
-
-		go func() {
-			defer wg.Done()
-			// read release via agent
-			rel, err := agent.GetRelease(releases[index].Name, 0, false)
-
-			if err != nil {
-				mu.Lock()
-				errors = append(errors, err.Error())
-				mu.Unlock()
-			}
-
-			if rel.Chart.Name() == "job" {
-				image := map[string]interface{}{}
-				image["repository"] = releases[index].ImageRepoURI
-				image["tag"] = form.Tag
-				rel.Config["image"] = image
-				rel.Config["paused"] = true
-
-				conf := &helm.UpgradeReleaseConfig{
-					Name:       releases[index].Name,
-					Cluster:    form.ReleaseForm.Cluster,
-					Repo:       app.Repo,
-					Registries: registries,
-					Values:     rel.Config,
-				}
-
-				_, err = agent.UpgradeReleaseByValues(conf, app.DOConf)
-
-				if err != nil {
-					mu.Lock()
-					errors = append(errors, err.Error())
-					mu.Unlock()
-				}
-			}
-		}()
-	}
-
-	wg.Wait()
-
-	w.WriteHeader(http.StatusOK)
-}
-
-// HandleRollbackRelease rolls a release back to a specified revision
-func (app *App) HandleRollbackRelease(w http.ResponseWriter, r *http.Request) {
-	name := chi.URLParam(r, "name")
-
-	vals, err := url.ParseQuery(r.URL.RawQuery)
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
-		return
-	}
-
-	form := &forms.RollbackReleaseForm{
-		ReleaseForm: &forms.ReleaseForm{
-			Form: &helm.Form{
-				Repo:              app.Repo,
-				DigitalOceanOAuth: app.DOConf,
-			},
-		},
-		Name: name,
-	}
-
-	form.ReleaseForm.PopulateHelmOptionsFromQueryParams(
-		vals,
-		app.Repo.Cluster(),
-	)
-
-	if err := json.NewDecoder(r.Body).Decode(form); err != nil {
-		app.handleErrorFormDecoding(err, ErrUserDecode, w)
-		return
-	}
-
-	agent, err := app.getAgentFromReleaseForm(
-		w,
-		r,
-		form.ReleaseForm,
-	)
-
-	// errors are handled in app.getAgentFromBodyParams
-	if err != nil {
-		return
-	}
-
-	err = agent.RollbackRelease(form.Name, form.Revision)
-
-	if err != nil {
-		app.sendExternalError(err, http.StatusInternalServerError, HTTPError{
-			Code:   ErrReleaseDeploy,
-			Errors: []string{"error rolling back release " + err.Error()},
-		}, w)
-
-		return
-	}
-
-	// get the full release data for GHA updating
-	rel, err := agent.GetRelease(form.Name, form.Revision, false)
-
-	if err != nil {
-		app.sendExternalError(err, http.StatusNotFound, HTTPError{
-			Code:   ErrReleaseReadData,
-			Errors: []string{"release not found"},
-		}, w)
-
-		return
-	}
-
-	// update the github actions env if the release exists and is built from source
-	if cName := rel.Chart.Metadata.Name; cName == "job" || cName == "web" || cName == "worker" {
-		clusterID, err := strconv.ParseUint(vals["cluster_id"][0], 10, 64)
-
-		if err != nil {
-			app.sendExternalError(err, http.StatusInternalServerError, HTTPError{
-				Code:   ErrReleaseReadData,
-				Errors: []string{"release not found"},
-			}, w)
-
-			return
-		}
-
-		release, err := app.Repo.Release().ReadRelease(uint(clusterID), name, rel.Namespace)
-
-		if release != nil {
-			// update image repo uri if changed
-			repository := rel.Config["image"].(map[string]interface{})["repository"]
-			repoStr, ok := repository.(string)
-
-			if !ok {
-				app.handleErrorInternal(fmt.Errorf("Could not find field repository in config"), w)
-				return
-			}
-
-			if repoStr != release.ImageRepoURI {
-				release, err = app.Repo.Release().UpdateRelease(release)
-
-				if err != nil {
-					app.handleErrorInternal(err, w)
-					return
-				}
-			}
-
-			gitAction := release.GitActionConfig
-
-			if gitAction.ID != 0 {
-				// parse env into build env
-				cEnv := &ContainerEnvConfig{}
-				rawValues, err := yaml.Marshal(rel.Config)
-
-				if err != nil {
-					app.sendExternalError(err, http.StatusInternalServerError, HTTPError{
-						Code:   ErrReleaseReadData,
-						Errors: []string{"could not get values of previous revision"},
-					}, w)
-				}
-
-				yaml.Unmarshal(rawValues, cEnv)
-
-				gr, err := app.Repo.GitRepo().ReadGitRepo(gitAction.GitRepoID)
-
-				if err != nil {
-					if err != gorm.ErrRecordNotFound {
-						app.handleErrorInternal(err, w)
-						return
-					}
-					gr = nil
-				}
-
-				repoSplit := strings.Split(gitAction.GitRepo, "/")
-
-				projID, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
-
-				if err != nil || projID == 0 {
-					app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-					return
-				}
-
-				gaRunner := &actions.GithubActions{
-					ServerURL:              app.ServerConf.ServerURL,
-					GithubOAuthIntegration: gr,
-					GithubInstallationID:   gitAction.GithubInstallationID,
-					GithubAppID:            app.GithubAppConf.AppID,
-					GithubAppSecretPath:    app.GithubAppConf.SecretPath,
-					GitRepoName:            repoSplit[1],
-					GitRepoOwner:           repoSplit[0],
-					Repo:                   app.Repo,
-					GithubConf:             app.GithubProjectConf,
-					ProjectID:              uint(projID),
-					ReleaseName:            name,
-					ReleaseNamespace:       release.Namespace,
-					GitBranch:              gitAction.GitBranch,
-					DockerFilePath:         gitAction.DockerfilePath,
-					FolderPath:             gitAction.FolderPath,
-					ImageRepoURL:           gitAction.ImageRepoURI,
-					BuildEnv:               cEnv.Container.Env.Normal,
-					ClusterID:              release.ClusterID,
-					Version:                gitAction.Version,
-				}
-
-				actionVersion, err := semver.NewVersion(gaRunner.Version)
-				if err != nil {
-					app.handleErrorInternal(err, w)
-				}
-
-				if createEnvSecretConstraint.Check(actionVersion) {
-					if err := gaRunner.CreateEnvSecret(); err != nil {
-						app.sendExternalError(err, http.StatusInternalServerError, HTTPError{
-							Code:   ErrReleaseReadData,
-							Errors: []string{"could not update github secret"},
-						}, w)
-					}
-				}
-			}
-		}
-	}
-
-	w.WriteHeader(http.StatusOK)
-}
-
-// HandleGetReleaseSteps returns a list of all steps for a given release
-// note that steps are not guaranteed to be in any specific order, so they should be ordered if needed
-func (app *App) HandleGetReleaseSteps(w http.ResponseWriter, r *http.Request) {
-	name := chi.URLParam(r, "name")
-	vals, err := url.ParseQuery(r.URL.RawQuery)
-
-	if err != nil {
-		app.handleErrorInternal(err, w)
-	}
-
-	namespace := vals["namespace"][0]
-	clusterId, err := strconv.ParseUint(vals["cluster_id"][0], 0, 64)
-
-	if err != nil {
-		app.handleErrorInternal(err, w)
-		return
-	}
-
-	rel, err := app.Repo.Release.ReadRelease(uint(clusterId), name, namespace)
-
-	if err != nil {
-		app.sendExternalError(err, http.StatusNotFound, HTTPError{
-			Code:   ErrReleaseReadData,
-			Errors: []string{"release not found"},
-		}, w)
-
-		return
-	}
-
-	res := make([]models.SubEventExternal, 0)
-
-	if rel.EventContainer != 0 {
-		subevents, err := app.Repo.Event.ReadEventsByContainerID(rel.EventContainer)
-
-		if err != nil {
-			app.handleErrorInternal(err, w)
-		}
-
-		for _, sub := range subevents {
-			res = append(res, sub.Externalize())
-		}
-	}
-
-	json.NewEncoder(w).Encode(res)
-}
-
-type HandleUpdateReleaseStepsForm struct {
-	Event struct {
-		ID     string             `json:"event_id" form:"required"`
-		Name   string             `json:"name" form:"required"`
-		Index  int64              `json:"index" form:"required"`
-		Status models.EventStatus `json:"status" form:"required"`
-		Info   string             `json:"info" form:"required"`
-	} `json:"event" form:"required"`
-	Name      string `json:"name"`
-	Namespace string `json:"namespace"`
-	ClusterID uint   `json:"cluster_id"`
-}
-
-// HandleUpdateReleaseSteps adds a new step to a release
-func (app *App) HandleUpdateReleaseSteps(w http.ResponseWriter, r *http.Request) {
-	form := &HandleUpdateReleaseStepsForm{}
-
-	if err := json.NewDecoder(r.Body).Decode(form); err != nil {
-		app.handleErrorInternal(err, w)
-		return
-	}
-
-	rel, err := app.Repo.Release.ReadRelease(form.ClusterID, form.Name, form.Namespace)
-
-	if err != nil {
-		app.sendExternalError(err, http.StatusInternalServerError, HTTPError{
-			Code:   ErrReleaseReadData,
-			Errors: []string{"Release not found"},
-		}, w)
-
-		return
-	}
-
-	if rel.EventContainer == 0 {
-		// create new event container
-		container, err := app.Repo.Event.CreateEventContainer(&models.EventContainer{ReleaseID: rel.ID})
-		if err != nil {
-			app.handleErrorDataWrite(err, w)
-			return
-		}
-
-		rel.EventContainer = container.ID
-
-		rel, err = app.Repo.Release.UpdateRelease(rel)
-
-		if err != nil {
-			app.handleErrorInternal(err, w)
-			return
-		}
-
-	}
-
-	container, err := app.Repo.Event.ReadEventContainer(rel.EventContainer)
-
-	if err != nil {
-		app.handleErrorInternal(err, w)
-		return
-	}
-
-	if err := app.Repo.Event.AppendEvent(container, &models.SubEvent{
-		EventContainerID: container.ID,
-		EventID:          form.Event.ID,
-		Name:             form.Event.Name,
-		Index:            form.Event.Index,
-		Status:           form.Event.Status,
-		Info:             form.Event.Info,
-	}); err != nil {
-		app.handleErrorInternal(err, w)
-	}
-
-}
-
-// ------------------------ Release handler helper functions ------------------------ //
-
-// getAgentFromQueryParams uses the query params to populate a form, and then
-// passes that form to the underlying app.getAgentFromReleaseForm to create a new
-// Helm agent.
-func (app *App) getAgentFromQueryParams(
-	w http.ResponseWriter,
-	r *http.Request,
-	form *forms.ReleaseForm,
-	// populate uses the query params to populate a form
-	populate ...func(vals url.Values, repo repository.ClusterRepository) error,
-) (*helm.Agent, error) {
-	vals, err := url.ParseQuery(r.URL.RawQuery)
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
-		return nil, err
-	}
-
-	for _, f := range populate {
-		err := f(vals, app.Repo.Cluster())
-
-		if err != nil {
-			app.handleErrorInternal(err, w)
-			return nil, err
-		}
-	}
-
-	return app.getAgentFromReleaseForm(w, r, form)
-}
-
-// getAgentFromReleaseForm uses a non-validated form to construct a new Helm agent based on
-// the userID found in the session and the options required by the Helm agent.
-func (app *App) getAgentFromReleaseForm(
-	w http.ResponseWriter,
-	r *http.Request,
-	form *forms.ReleaseForm,
-) (*helm.Agent, error) {
-	var err error
-
-	// validate the form
-	if err := app.validator.Struct(form); err != nil {
-		app.handleErrorFormValidation(err, ErrReleaseValidateFields, w)
-		return nil, err
-	}
-
-	// create a new agent
-	var agent *helm.Agent
-
-	if app.ServerConf.IsTesting {
-		agent = app.TestAgents.HelmAgent
-	} else {
-		agent, err = helm.GetAgentOutOfClusterConfig(form.Form, app.Logger)
-	}
-
-	if err != nil {
-		app.handleErrorInternal(err, w)
-	}
-
-	return agent, err
-}
-
-const veleroForm string = `tags:
-- hello
-tabs:
-- name: main
-  context:
-    type: cluster
-    config:
-      group: velero.io
-      version: v1
-      resource: backups
-  label: Backups
-  sections:
-  - name: section_one
-    contents: 
-    - type: heading
-      label: 💾 Velero Backups
-    - type: resource-list
-      value: |
-        .items[] | { 
-          name: .metadata.name, 
-          label: .metadata.namespace,
-          status: .status.phase,
-          timestamp: .status.completionTimestamp,
-          message: [
-            (if .status.volumeSnapshotsAttempted then "\(.status.volumeSnapshotsAttempted) volume snapshots attempted, \(.status.volumeSnapshotsCompleted) completed." else null end),
-            "Finished \(.status.completionTimestamp).",
-            "Backup expires on \(.status.expiration)."
-          ]|join(" "),
-          data: {
-            "Included Namespaces": (if .spec.includedNamespaces then .spec.includedNamespaces|join(",") else "* (all)" end),
-            "Included Resources": (if .spec.includedResources then .spec.includedResources|join(",") else "* (all)" end),
-            "Storage Location": .spec.storageLocation
-          }
-        }`

+ 0 - 507
server/api/release_handler_test.go

@@ -1,507 +0,0 @@
-package api_test
-
-// import (
-// 	"encoding/json"
-// 	"net/http"
-// 	"net/http/httptest"
-// 	"net/url"
-// 	"reflect"
-// 	"strings"
-// 	"testing"
-
-// 	"github.com/porter-dev/porter/internal/helm"
-
-// 	"helm.sh/helm/v3/pkg/chart"
-// 	"helm.sh/helm/v3/pkg/release"
-// 	"helm.sh/helm/v3/pkg/storage/driver"
-// )
-
-// type releaseStub struct {
-// 	name           string
-// 	namespace      string
-// 	version        int
-// 	releaseVersion string
-// 	status         release.Status
-// }
-
-// // ------------------------- TEST TYPES AND MAIN LOOP ------------------------- //
-
-// type releaseTest struct {
-// 	initializers []func(tester *tester)
-// 	namespace    string
-// 	msg          string
-// 	method       string
-// 	endpoint     string
-// 	body         string
-// 	expStatus    int
-// 	expBody      string
-// 	useCookie    bool
-// 	validators   []func(c *releaseTest, tester *tester, t *testing.T)
-// }
-
-// func testReleaseRequests(t *testing.T, tests []*releaseTest, canQuery bool) {
-// 	for _, c := range tests {
-// 		// create a new tester
-// 		tester := newTester(canQuery)
-
-// 		// if there's an initializer, call it
-// 		for _, init := range c.initializers {
-// 			init(tester)
-// 		}
-
-// 		tester.app.TestAgents.HelmAgent.ActionConfig.Releases.Driver.(*driver.Memory).SetNamespace(c.namespace)
-
-// 		req, err := http.NewRequest(
-// 			c.method,
-// 			c.endpoint,
-// 			strings.NewReader(c.body),
-// 		)
-
-// 		tester.req = req
-
-// 		if c.useCookie {
-// 			req.AddCookie(tester.cookie)
-// 		}
-
-// 		if err != nil {
-// 			t.Fatal(err)
-// 		}
-
-// 		tester.execute()
-// 		rr := tester.rr
-
-// 		// first, check that the status matches
-// 		if status := rr.Code; status != c.expStatus {
-// 			t.Errorf("%s, handler returned wrong status code: got %v want %v",
-// 				c.msg, status, c.expStatus)
-// 		}
-
-// 		// if there's a validator, call it
-// 		for _, validate := range c.validators {
-// 			validate(c, tester, t)
-// 		}
-// 	}
-// }
-
-// // ------------------------- TEST FIXTURES AND FUNCTIONS  ------------------------- //
-
-// var listReleasesTests = []*releaseTest{
-// 	{
-// 		initializers: []func(tester *tester){
-// 			initDefaultReleases,
-// 		},
-// 		msg:    "List releases no namespace",
-// 		method: "GET",
-// 		endpoint: "/api/projects/1/releases?" + url.Values{
-// 			"namespace":    []string{""},
-// 			"cluster_id":   []string{"1"},
-// 			"storage":      []string{"memory"},
-// 			"limit":        []string{"20"},
-// 			"skip":         []string{"0"},
-// 			"byDate":       []string{"false"},
-// 			"statusFilter": []string{"deployed"},
-// 		}.Encode(),
-// 		body:      "",
-// 		expStatus: http.StatusOK,
-// 		expBody:   releaseStubsToReleaseJSON(sampleReleaseStubs),
-// 		useCookie: true,
-// 		validators: []func(c *releaseTest, tester *tester, t *testing.T){
-// 			releaseReleaseArrBodyValidator,
-// 		},
-// 	},
-// 	{
-// 		initializers: []func(tester *tester){
-// 			initDefaultReleases,
-// 		},
-// 		msg:       "List releases with namespace",
-// 		method:    "GET",
-// 		namespace: "default",
-// 		endpoint: "/api/projects/1/releases?" + url.Values{
-// 			"namespace":    []string{"default"},
-// 			"cluster_id":   []string{"1"},
-// 			"storage":      []string{"memory"},
-// 			"limit":        []string{"20"},
-// 			"skip":         []string{"0"},
-// 			"byDate":       []string{"false"},
-// 			"statusFilter": []string{"deployed"},
-// 		}.Encode(),
-// 		body:      "",
-// 		expStatus: http.StatusOK,
-// 		expBody: releaseStubsToReleaseJSON([]releaseStub{
-// 			sampleReleaseStubs[0],
-// 			sampleReleaseStubs[2],
-// 		}),
-// 		useCookie: true,
-// 		validators: []func(c *releaseTest, tester *tester, t *testing.T){
-// 			releaseReleaseArrBodyValidator,
-// 		},
-// 	},
-// }
-
-// func TestHandleListReleases(t *testing.T) {
-// 	testReleaseRequests(t, listReleasesTests, true)
-// }
-
-// var getReleaseTests = []*releaseTest{
-// 	{
-// 		initializers: []func(tester *tester){
-// 			initDefaultReleases,
-// 		},
-// 		msg:       "Get releases",
-// 		method:    "GET",
-// 		namespace: "default",
-// 		endpoint: "/api/projects/1/releases/airwatch/1?" + url.Values{
-// 			"namespace":  []string{"default"},
-// 			"cluster_id": []string{"1"},
-// 			"storage":    []string{"memory"},
-// 		}.Encode(),
-// 		body:      "",
-// 		expStatus: http.StatusOK,
-// 		expBody:   releaseStubToReleaseJSON(sampleReleaseStubs[0]),
-// 		useCookie: true,
-// 		validators: []func(c *releaseTest, tester *tester, t *testing.T){
-// 			releaseReleaseBodyValidator,
-// 		},
-// 	},
-// 	{
-// 		initializers: []func(tester *tester){
-// 			initDefaultReleases,
-// 		},
-// 		msg:       "Release not found",
-// 		method:    "GET",
-// 		namespace: "default",
-// 		endpoint: "/api/projects/1/releases/airwatch/5?" + url.Values{
-// 			"namespace":  []string{""},
-// 			"cluster_id": []string{"1"},
-// 			"storage":    []string{"memory"},
-// 		}.Encode(),
-// 		body:      "",
-// 		expStatus: http.StatusNotFound,
-// 		expBody:   `{"code":602,"errors":["release not found"]}`,
-// 		useCookie: true,
-// 		validators: []func(c *releaseTest, tester *tester, t *testing.T){
-// 			releaseBasicBodyValidator,
-// 		},
-// 	},
-// }
-
-// func TestHandleGetRelease(t *testing.T) {
-// 	testReleaseRequests(t, getReleaseTests, true)
-// }
-
-// var listReleaseHistoryTests = []*releaseTest{
-// 	{
-// 		initializers: []func(tester *tester){
-// 			initHistoryReleases,
-// 		},
-// 		msg:       "List release history",
-// 		method:    "GET",
-// 		namespace: "default",
-// 		endpoint: "/api/projects/1/releases/wordpress/history?" + url.Values{
-// 			"namespace":  []string{""},
-// 			"cluster_id": []string{"1"},
-// 			"storage":    []string{"memory"},
-// 		}.Encode(),
-// 		body:      "",
-// 		expStatus: http.StatusOK,
-// 		expBody:   releaseStubsToReleaseJSON(historyReleaseStubs),
-// 		useCookie: true,
-// 		validators: []func(c *releaseTest, tester *tester, t *testing.T){
-// 			releaseReleaseArrBodyValidator,
-// 		},
-// 	},
-// 	{
-// 		initializers: []func(tester *tester){
-// 			initDefaultReleases,
-// 		},
-// 		msg:       "Release not found",
-// 		method:    "GET",
-// 		namespace: "default",
-// 		endpoint: "/api/projects/1/releases/asldfkja/history?" + url.Values{
-// 			"namespace":  []string{""},
-// 			"cluster_id": []string{"1"},
-// 			"storage":    []string{"memory"},
-// 		}.Encode(),
-// 		body:      "",
-// 		expStatus: http.StatusNotFound,
-// 		expBody:   `{"code":602,"errors":["release not found"]}`,
-// 		useCookie: true,
-// 		validators: []func(c *releaseTest, tester *tester, t *testing.T){
-// 			releaseBasicBodyValidator,
-// 		},
-// 	},
-// }
-
-// func TestHandleListReleaseHistory(t *testing.T) {
-// 	testReleaseRequests(t, listReleaseHistoryTests, true)
-// }
-
-// var upgradeReleaseTests = []*releaseTest{
-// 	{
-// 		initializers: []func(tester *tester){
-// 			initHistoryReleases,
-// 		},
-// 		msg:       "Upgrade relase",
-// 		method:    "POST",
-// 		namespace: "default",
-// 		endpoint: "/api/projects/1/releases/wordpress/upgrade?" + url.Values{
-// 			"cluster_id": []string{"1"},
-// 		}.Encode(),
-// 		body: `
-// 			{
-// 				"namespace": "default",
-// 				"storage": "memory",
-// 				"values": "\nfoo: bar\n"
-// 			}
-// 		`,
-// 		expStatus: http.StatusOK,
-// 		expBody:   ``,
-// 		useCookie: true,
-// 		validators: []func(c *releaseTest, tester *tester, t *testing.T){
-// 			func(c *releaseTest, tester *tester, t *testing.T) {
-// 				req, err := http.NewRequest(
-// 					"GET",
-// 					"/api/projects/1/releases/wordpress/3?"+url.Values{
-// 						"namespace":  []string{"default"},
-// 						"cluster_id": []string{"1"},
-// 						"storage":    []string{"memory"},
-// 					}.Encode(),
-// 					strings.NewReader(""),
-// 				)
-
-// 				req.AddCookie(tester.cookie)
-
-// 				if err != nil {
-// 					t.Fatal(err)
-// 				}
-
-// 				rr2 := httptest.NewRecorder()
-// 				tester.router.ServeHTTP(rr2, req)
-
-// 				gotBody := &release.Release{}
-// 				expBody := &release.Release{}
-
-// 				expBodyJSON := releaseStubToReleaseJSON(releaseStub{"wordpress", "default", 3, "1.0.2", release.StatusDeployed})
-
-// 				json.Unmarshal(rr2.Body.Bytes(), gotBody)
-// 				json.Unmarshal([]byte(expBodyJSON), expBody)
-
-// 				// just check name and version match, other items will be different
-// 				if gotBody.Name != expBody.Name {
-// 					t.Errorf("%s, validation wrong body: got %v want %v",
-// 						c.msg, gotBody.Name, expBody.Name)
-// 				}
-
-// 				if gotBody.Version != expBody.Version {
-// 					t.Errorf("%s, validation wrong body: got %v want %v",
-// 						c.msg, gotBody.Version, expBody.Version)
-// 				}
-
-// 				expConfig := map[string]interface{}{
-// 					"foo": "bar",
-// 				}
-
-// 				if !reflect.DeepEqual(gotBody.Config, expConfig) {
-// 					t.Errorf("%s, validation wrong config: got %v want %v",
-// 						c.msg, gotBody.Config, expConfig)
-// 				}
-// 			},
-// 		},
-// 	},
-// }
-
-// func TestUpgradeRelease(t *testing.T) {
-// 	testReleaseRequests(t, upgradeReleaseTests, true)
-// }
-
-// var rollbackReleaseTests = []*releaseTest{
-// 	{
-// 		initializers: []func(tester *tester){
-// 			initHistoryReleases,
-// 		},
-// 		msg:       "Rollback release",
-// 		method:    "POST",
-// 		namespace: "default",
-// 		endpoint: "/api/projects/1/releases/wordpress/rollback?" + url.Values{
-// 			"cluster_id": []string{"1"},
-// 		}.Encode(),
-// 		body: `
-// 			{
-// 				"namespace": "default",
-// 				"storage": "memory",
-// 				"revision": 1
-// 			}
-// 		`,
-// 		expStatus: http.StatusOK,
-// 		expBody:   ``,
-// 		useCookie: true,
-// 		validators: []func(c *releaseTest, tester *tester, t *testing.T){
-// 			func(c *releaseTest, tester *tester, t *testing.T) {
-// 				req, err := http.NewRequest(
-// 					"GET",
-// 					"/api/projects/1/releases/wordpress/3?"+url.Values{
-// 						"namespace":  []string{"default"},
-// 						"cluster_id": []string{"1"},
-// 						"storage":    []string{"memory"},
-// 					}.Encode(),
-// 					strings.NewReader(""),
-// 				)
-
-// 				req.AddCookie(tester.cookie)
-
-// 				if err != nil {
-// 					t.Fatal(err)
-// 				}
-
-// 				rr2 := httptest.NewRecorder()
-// 				tester.router.ServeHTTP(rr2, req)
-
-// 				gotBody := &release.Release{}
-// 				expBody := &release.Release{}
-
-// 				expBodyJSON := releaseStubToReleaseJSON(releaseStub{"wordpress", "default", 3, "1.0.1", release.StatusDeployed})
-
-// 				json.Unmarshal(rr2.Body.Bytes(), gotBody)
-// 				json.Unmarshal([]byte(expBodyJSON), expBody)
-
-// 				// just check name and version match, other items will be different
-// 				if gotBody.Name != expBody.Name {
-// 					t.Errorf("%s, validation wrong body: got %v want %v",
-// 						c.msg, gotBody.Name, expBody.Name)
-// 				}
-
-// 				if gotBody.Version != expBody.Version {
-// 					t.Errorf("%s, validation wrong body: got %v want %v",
-// 						c.msg, gotBody.Version, expBody.Version)
-// 				}
-// 			},
-// 		},
-// 	},
-// }
-
-// func TestRollbackRelease(t *testing.T) {
-// 	testReleaseRequests(t, rollbackReleaseTests, true)
-// }
-
-// // ------------------------- INITIALIZERS AND VALIDATORS ------------------------- //
-
-// func initDefaultReleases(tester *tester) {
-// 	initUserDefault(tester)
-// 	initProject(tester)
-// 	initProjectClusterDefault(tester)
-
-// 	agent := tester.app.TestAgents.HelmAgent
-
-// 	makeReleases(agent, sampleReleaseStubs)
-
-// 	// calling agent.ActionConfig.Releases.Create in makeReleases will automatically set the
-// 	// namespace, so we have to reset the namespace of the storage driver
-// 	agent.ActionConfig.Releases.Driver.(*driver.Memory).SetNamespace("")
-// }
-
-// func initHistoryReleases(tester *tester) {
-// 	initUserDefault(tester)
-// 	initProject(tester)
-// 	initProjectClusterDefault(tester)
-
-// 	agent := tester.app.TestAgents.HelmAgent
-
-// 	makeReleases(agent, historyReleaseStubs)
-
-// 	// calling agent.ActionConfig.Releases.Create in makeReleases will automatically set the
-// 	// namespace, so we have to reset the namespace of the storage driver
-// 	agent.ActionConfig.Releases.Driver.(*driver.Memory).SetNamespace("")
-// }
-
-// var sampleReleaseStubs = []releaseStub{
-// 	{"airwatch", "default", 1, "1.0.0", release.StatusDeployed},
-// 	{"not-in-default-namespace", "other", 1, "1.0.1", release.StatusDeployed},
-// 	{"wordpress", "default", 1, "1.0.2", release.StatusDeployed},
-// }
-
-// var historyReleaseStubs = []releaseStub{
-// 	{"wordpress", "default", 1, "1.0.1", release.StatusSuperseded},
-// 	{"wordpress", "default", 2, "1.0.2", release.StatusDeployed},
-// }
-
-// func releaseStubsToReleaseJSON(rels []releaseStub) string {
-// 	releases := make([]*release.Release, 0)
-
-// 	for _, r := range rels {
-// 		rel := releaseStubToRelease(r)
-
-// 		releases = append(releases, rel)
-// 	}
-
-// 	str, _ := json.Marshal(releases)
-
-// 	return string(str)
-// }
-
-// func releaseStubToReleaseJSON(r releaseStub) string {
-// 	rel := releaseStubToRelease(r)
-
-// 	str, _ := json.Marshal(rel)
-
-// 	return string(str)
-// }
-
-// func releaseStubToRelease(r releaseStub) *release.Release {
-// 	return &release.Release{
-// 		Name:      r.name,
-// 		Namespace: r.namespace,
-// 		Version:   r.version,
-// 		Info: &release.Info{
-// 			Status: r.status,
-// 		},
-// 		Chart: &chart.Chart{
-// 			Metadata: &chart.Metadata{
-// 				Version: r.releaseVersion,
-// 				Icon:    "https://example.com/icon.png",
-// 			},
-// 		},
-// 	}
-// }
-
-// func makeReleases(agent *helm.Agent, rels []releaseStub) {
-// 	storage := agent.ActionConfig.Releases
-
-// 	for _, r := range rels {
-// 		rel := releaseStubToRelease(r)
-
-// 		storage.Create(rel)
-// 	}
-// }
-
-// func releaseBasicBodyValidator(c *releaseTest, tester *tester, t *testing.T) {
-// 	if body := tester.rr.Body.String(); strings.TrimSpace(body) != strings.TrimSpace(c.expBody) {
-// 		t.Errorf("%s, handler returned wrong body: got %v want %v",
-// 			c.msg, body, c.expBody)
-// 	}
-// }
-
-// func releaseReleaseBodyValidator(c *releaseTest, tester *tester, t *testing.T) {
-// 	gotBody := &release.Release{}
-// 	expBody := &release.Release{}
-
-// 	json.Unmarshal(tester.rr.Body.Bytes(), gotBody)
-// 	json.Unmarshal([]byte(c.expBody), expBody)
-
-// 	if !reflect.DeepEqual(gotBody, expBody) {
-// 		t.Errorf("%s, handler returned wrong body: got %v want %v",
-// 			c.msg, gotBody, expBody)
-// 	}
-// }
-
-// func releaseReleaseArrBodyValidator(c *releaseTest, tester *tester, t *testing.T) {
-// 	gotBody := &[]release.Release{}
-// 	expBody := &[]release.Release{}
-
-// 	json.Unmarshal(tester.rr.Body.Bytes(), gotBody)
-// 	json.Unmarshal([]byte(c.expBody), expBody)
-
-// 	if !reflect.DeepEqual(gotBody, expBody) {
-// 		t.Errorf("%s, handler returned wrong body: got %v want %v",
-// 			c.msg, gotBody, expBody)
-// 	}
-// }

+ 0 - 166
server/api/template_handler.go

@@ -1,166 +0,0 @@
-package api
-
-import (
-	"encoding/json"
-	"net/http"
-	"net/url"
-	"strings"
-
-	"github.com/go-chi/chi"
-	"github.com/porter-dev/porter/internal/forms"
-	"github.com/porter-dev/porter/internal/helm/loader"
-	"github.com/porter-dev/porter/internal/helm/upgrade"
-	"github.com/porter-dev/porter/internal/templater/parser"
-
-	"github.com/porter-dev/porter/internal/models"
-)
-
-// HandleListTemplates retrieves a list of Porter templates
-// TODO: test and reduce fragility (handle untar/parse error for individual charts)
-// TODO: separate markdown retrieval into its own query if necessary
-func (app *App) HandleListTemplates(w http.ResponseWriter, r *http.Request) {
-
-	vals, err := url.ParseQuery(r.URL.RawQuery)
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
-		return
-	}
-
-	repoURL := app.ServerConf.DefaultApplicationHelmRepoURL
-
-	if inputRepoURL, ok := vals["repo_url"]; ok && len(inputRepoURL) == 1 {
-		repoURL = inputRepoURL[0]
-	}
-
-	repoIndex, err := loader.LoadRepoIndexPublic(repoURL)
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
-		return
-	}
-
-	porterCharts := loader.RepoIndexToPorterChartList(repoIndex)
-
-	json.NewEncoder(w).Encode(porterCharts)
-}
-
-// HandleReadTemplate reads a given template with name and version field
-func (app *App) HandleReadTemplate(w http.ResponseWriter, r *http.Request) {
-	name := chi.URLParam(r, "name")
-	version := chi.URLParam(r, "version")
-
-	// if version passed as latest, pass empty string to loader to get latest
-	if version == "latest" {
-		version = ""
-	}
-
-	form := &forms.ChartForm{
-		Name:    name,
-		Version: version,
-		RepoURL: app.ServerConf.DefaultApplicationHelmRepoURL,
-	}
-
-	// if a repo_url is passed as query param, it will be populated
-	vals, err := url.ParseQuery(r.URL.RawQuery)
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
-		return
-	}
-
-	form.PopulateRepoURLFromQueryParams(vals)
-
-	chart, err := loader.LoadChartPublic(form.RepoURL, form.Name, form.Version)
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
-		return
-	}
-
-	parserDef := &parser.ClientConfigDefault{
-		HelmChart: chart,
-	}
-
-	res := &models.PorterChartRead{}
-	res.Metadata = chart.Metadata
-	res.Values = chart.Values
-
-	for _, file := range chart.Files {
-		if strings.Contains(file.Name, "form.yaml") {
-			formYAML, err := parser.FormYAMLFromBytes(parserDef, file.Data, "declared")
-
-			if err != nil {
-				break
-			}
-
-			res.Form = formYAML
-		} else if strings.Contains(file.Name, "README.md") {
-			res.Markdown = string(file.Data)
-		}
-	}
-
-	json.NewEncoder(w).Encode(res)
-}
-
-// HandleGetTemplateUpgradeNotes gets the upgrade notes for a template
-func (app *App) HandleGetTemplateUpgradeNotes(w http.ResponseWriter, r *http.Request) {
-	name := chi.URLParam(r, "name")
-	version := chi.URLParam(r, "version")
-
-	// if version passed as latest, pass empty string to loader to get latest
-	if version == "latest" {
-		version = ""
-	}
-
-	form := &forms.ChartForm{
-		Name:    name,
-		Version: version,
-		RepoURL: app.ServerConf.DefaultApplicationHelmRepoURL,
-	}
-
-	// look for the prev_version in the query params
-	vals, err := url.ParseQuery(r.URL.RawQuery)
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
-		return
-	}
-
-	form.PopulateRepoURLFromQueryParams(vals)
-
-	prevVersion := "v0.0.0"
-
-	if prevVersionArr, ok := vals["prev_version"]; ok && len(prevVersionArr) == 1 {
-		prevVersion = prevVersionArr[0]
-	}
-
-	chart, err := loader.LoadChartPublic(form.RepoURL, form.Name, form.Version)
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
-		return
-	}
-
-	res := &upgrade.UpgradeFile{}
-
-	for _, file := range chart.Files {
-		if strings.Contains(file.Name, "upgrade.yaml") {
-			upgradeFile, err := upgrade.ParseUpgradeFileFromBytes(file.Data)
-
-			if err != nil {
-				break
-			}
-
-			upgradeFile, err = upgradeFile.GetUpgradeFileBetweenVersions(prevVersion, version)
-
-			if err != nil {
-				break
-			}
-
-			res = upgradeFile
-		}
-	}
-
-	json.NewEncoder(w).Encode(res)
-}

+ 0 - 123
server/api/template_handler_test.go

@@ -1,123 +0,0 @@
-package api_test
-
-import (
-	"encoding/json"
-	"net/http"
-	"strings"
-	"testing"
-
-	"github.com/go-test/deep"
-	"github.com/porter-dev/porter/internal/models"
-)
-
-// ------------------------- TEST TYPES AND MAIN LOOP ------------------------- //
-
-type templateTest struct {
-	initializers []func(tester *tester)
-	msg          string
-	method       string
-	endpoint     string
-	body         string
-	expStatus    int
-	expBody      string
-	useCookie    bool
-	validators   []func(c *templateTest, tester *tester, t *testing.T)
-}
-
-func testTemplatesRequests(t *testing.T, tests []*templateTest, canQuery bool) {
-	for _, c := range tests {
-		// create a new tester
-		tester := newTester(canQuery)
-
-		// if there's an initializer, call it
-		for _, init := range c.initializers {
-			init(tester)
-		}
-
-		req, err := http.NewRequest(
-			c.method,
-			c.endpoint,
-			strings.NewReader(c.body),
-		)
-
-		tester.req = req
-
-		if c.useCookie {
-			req.AddCookie(tester.cookie)
-		}
-
-		if err != nil {
-			t.Fatal(err)
-		}
-
-		tester.execute()
-		rr := tester.rr
-
-		// first, check that the status matches
-		if status := rr.Code; status != c.expStatus {
-			t.Errorf("%s, handler returned wrong status code: got %v want %v",
-				c.msg, status, c.expStatus)
-		}
-
-		// if there's a validator, call it
-		for _, validate := range c.validators {
-			validate(c, tester, t)
-		}
-	}
-}
-
-// ------------------------- TEST FIXTURES AND FUNCTIONS  ------------------------- //
-
-// var listTemplatesTests = []*templateTest{
-// 	&templateTest{
-// 		initializers: []func(tester *tester){
-// 			initUserDefault,
-// 		},
-// 		msg:       "List templates",
-// 		method:    "GET",
-// 		endpoint:  "/api/templates",
-// 		body:      "",
-// 		expStatus: http.StatusOK,
-// 		expBody:   `[{"name":"Docker","description":"Template to deploy any Docker container on Porter.","icon":"https://cdn4.iconfinder.com/data/icons/logos-and-brands/512/97_Docker_logo_logos-512.png"},{"name":"redis","description":"DEPRECATED Open source, advanced key-value store. It is often referred to as a data structure server since keys can contain strings, hashes, lists, sets and sorted sets.","icon":"https://bitnami.com/assets/stacks/redis/img/redis-stack-220x234.png"}]`,
-// 		useCookie: true,
-// 		validators: []func(c *templateTest, tester *tester, t *testing.T){
-// 			templatesListValidator,
-// 		},
-// 	},
-// }
-
-// func TestHandleListTemplates(t *testing.T) {
-// 	testTemplatesRequests(t, listTemplatesTests, true)
-// }
-
-// ------------------------- INITIALIZERS AND VALIDATORS ------------------------- //
-
-func templatesListValidator(c *templateTest, tester *tester, t *testing.T) {
-	gotBody := make([]*models.PorterChartList, 0)
-	expBody := make([]*models.PorterChartList, 0)
-
-	json.Unmarshal(tester.rr.Body.Bytes(), &gotBody)
-	json.Unmarshal([]byte(c.expBody), &expBody)
-
-	if diff := deep.Equal(gotBody, expBody); diff != nil {
-		t.Errorf("handler returned wrong body:\n")
-		t.Error(diff)
-	}
-}
-
-func templateBodyValidator(c *templateTest, tester *tester, t *testing.T) {
-	gotBody := models.PorterChartRead{}
-	expBody := models.PorterChartRead{}
-
-	bytes := tester.rr.Body.Bytes()
-
-	t.Errorf(string(bytes))
-
-	json.Unmarshal(bytes, &gotBody)
-	json.Unmarshal([]byte(c.expBody), &expBody)
-
-	if diff := deep.Equal(gotBody, expBody); diff != nil {
-		t.Errorf("handler returned wrong body:\n")
-		t.Error(diff)
-	}
-}

+ 0 - 901
server/api/user_handler.go

@@ -1,901 +0,0 @@
-package api
-
-import (
-	"encoding/json"
-	"errors"
-	"fmt"
-	"net/http"
-	"net/url"
-	"strconv"
-	"strings"
-	"time"
-
-	"golang.org/x/crypto/bcrypt"
-
-	"gorm.io/gorm"
-
-	"github.com/go-chi/chi"
-	"github.com/porter-dev/porter/api/types"
-	"github.com/porter-dev/porter/internal/analytics"
-	"github.com/porter-dev/porter/internal/auth/token"
-	"github.com/porter-dev/porter/internal/forms"
-	"github.com/porter-dev/porter/internal/models"
-	"github.com/porter-dev/porter/internal/notifier"
-	"github.com/porter-dev/porter/internal/repository"
-)
-
-// Enumeration of user API error codes, represented as int64
-const (
-	ErrUserDecode ErrorCode = iota + 600
-	ErrUserValidateFields
-	ErrUserDataRead
-)
-
-// HandleCreateUser validates a user form entry, converts the user to a gorm
-// model, and saves the user to the database
-func (app *App) HandleCreateUser(w http.ResponseWriter, r *http.Request) {
-	session, err := app.Store.Get(r, app.ServerConf.CookieName)
-
-	if err != nil {
-		app.handleErrorDataRead(err, w)
-	}
-
-	form := &forms.CreateUserForm{
-		// if app can send email verification, set the email verified to false
-		EmailVerified: !app.Capabilities.Email,
-	}
-
-	user, err := app.writeUser(
-		form,
-		app.Repo.User().CreateUser,
-		w,
-		r,
-		doesUserExist,
-	)
-
-	if err == nil {
-		// send to segment
-		app.AnalyticsClient.Identify(analytics.CreateSegmentIdentifyUser(user))
-
-		app.AnalyticsClient.Track(analytics.UserCreateTrack(&analytics.UserCreateTrackOpts{
-			UserScopedTrackOpts: analytics.GetUserScopedTrackOpts(user.ID),
-			Email:               user.Email,
-		}))
-
-		app.Logger.Info().Msgf("New user created: %d", user.ID)
-
-		// non-fatal email verification flow
-		app.startEmailVerificationFlow(user)
-
-		var redirect string
-
-		if valR := session.Values["redirect"]; valR != nil {
-			redirect = session.Values["redirect"].(string)
-		}
-
-		session.Values["authenticated"] = true
-		session.Values["user_id"] = user.ID
-		session.Values["email"] = user.Email
-		session.Values["redirect"] = ""
-		session.Save(r, w)
-
-		w.WriteHeader(http.StatusCreated)
-
-		if err := app.sendUser(w, user.ID, user.Email, false, redirect); err != nil {
-			app.handleErrorFormDecoding(err, ErrUserDecode, w)
-			return
-		}
-	}
-}
-
-// HandleAuthCheck checks whether current session is authenticated and returns user ID if so.
-func (app *App) HandleAuthCheck(w http.ResponseWriter, r *http.Request) {
-	// first, check for token
-	tok := app.getTokenFromRequest(r)
-
-	if tok != nil {
-		// read the user
-		user, err := app.Repo.User().ReadUser(tok.IBy)
-
-		if err != nil {
-			http.Error(w, err.Error(), http.StatusInternalServerError)
-			return
-		}
-
-		if err := app.sendUser(w, tok.IBy, user.Email, user.EmailVerified, ""); err != nil {
-			app.handleErrorFormDecoding(err, ErrUserDecode, w)
-			return
-		}
-
-		return
-	}
-
-	session, err := app.Store.Get(r, app.ServerConf.CookieName)
-
-	if err != nil {
-		http.Error(w, err.Error(), http.StatusInternalServerError)
-		return
-	}
-
-	userID, _ := session.Values["user_id"].(uint)
-	email, _ := session.Values["email"].(string)
-
-	user, err := app.Repo.User().ReadUser(userID)
-
-	if err != nil {
-		http.Error(w, err.Error(), http.StatusInternalServerError)
-		return
-	}
-
-	w.WriteHeader(http.StatusOK)
-
-	if err := app.sendUser(w, userID, email, user.EmailVerified, ""); err != nil {
-		app.handleErrorFormDecoding(err, ErrUserDecode, w)
-		return
-	}
-}
-
-// HandleCLILoginUser verifies that a user is logged in, and generates an access
-// token for usage from the CLI
-func (app *App) HandleCLILoginUser(w http.ResponseWriter, r *http.Request) {
-	queryParams, _ := url.ParseQuery(r.URL.RawQuery)
-
-	redirect := queryParams["redirect"][0]
-
-	session, err := app.Store.Get(r, app.ServerConf.CookieName)
-
-	if err != nil {
-		http.Error(w, err.Error(), http.StatusInternalServerError)
-		return
-	}
-
-	userID, _ := session.Values["user_id"].(uint)
-
-	// generate the token
-	jwt, err := token.GetTokenForUser(userID)
-
-	if err != nil {
-		app.handleErrorInternal(err, w)
-		return
-	}
-
-	encoded, err := jwt.EncodeToken(&token.TokenGeneratorConf{
-		TokenSecret: app.ServerConf.TokenGeneratorSecret,
-	})
-
-	if err != nil {
-		app.handleErrorInternal(err, w)
-		return
-	}
-
-	// generate 64 characters long authorization code
-	code, err := repository.GenerateRandomBytes(32)
-
-	if err != nil {
-		app.handleErrorInternal(err, w)
-		return
-	}
-
-	expiry := time.Now().Add(30 * time.Second)
-
-	// create auth code object and send back authorization code
-	authCode := &models.AuthCode{
-		Token:             encoded,
-		AuthorizationCode: code,
-		Expiry:            &expiry,
-	}
-
-	authCode, err = app.Repo.AuthCode().CreateAuthCode(authCode)
-
-	if err != nil {
-		app.handleErrorInternal(err, w)
-		return
-	}
-
-	http.Redirect(w, r, fmt.Sprintf("%s/?code=%s", redirect, url.QueryEscape(authCode.AuthorizationCode)), 302)
-}
-
-type ExchangeRequest struct {
-	AuthorizationCode string `json:"authorization_code"`
-}
-
-type ExchangeResponse struct {
-	Token string `json:"token"`
-}
-
-// HandleCLILoginExchangeToken exchanges an authorization code for a token
-func (app *App) HandleCLILoginExchangeToken(w http.ResponseWriter, r *http.Request) {
-	// read the request body and look up the authorization token
-	req := &ExchangeRequest{}
-
-	if err := json.NewDecoder(r.Body).Decode(req); err != nil {
-		app.handleErrorFormDecoding(err, ErrUserDecode, w)
-		return
-	}
-
-	authCode, err := app.Repo.AuthCode().ReadAuthCode(req.AuthorizationCode)
-
-	if err != nil || authCode.IsExpired() {
-		http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
-		return
-	}
-
-	res := &ExchangeResponse{
-		Token: authCode.Token,
-	}
-
-	w.WriteHeader(http.StatusOK)
-
-	if err := json.NewEncoder(w).Encode(res); err != nil {
-		app.handleErrorFormDecoding(err, ErrUserDecode, w)
-		return
-	}
-}
-
-// HandleLoginUser checks the request header for cookie and validates the user.
-func (app *App) HandleLoginUser(w http.ResponseWriter, r *http.Request) {
-	session, err := app.Store.Get(r, app.ServerConf.CookieName)
-
-	if err != nil {
-		app.handleErrorDataRead(err, w)
-		return
-	}
-
-	form := &forms.LoginUserForm{}
-	// decode from JSON to form value
-	if err := json.NewDecoder(r.Body).Decode(form); err != nil {
-		app.handleErrorFormDecoding(err, ErrUserDecode, w)
-		return
-	}
-
-	storedUser, readErr := app.Repo.User().ReadUserByEmail(form.Email)
-
-	if readErr != nil {
-		app.sendExternalError(readErr, http.StatusUnauthorized, HTTPError{
-			Errors: []string{"email not registered"},
-			Code:   http.StatusUnauthorized,
-		}, w)
-
-		return
-	}
-
-	if err := bcrypt.CompareHashAndPassword([]byte(storedUser.Password), []byte(form.Password)); err != nil {
-		app.sendExternalError(readErr, http.StatusUnauthorized, HTTPError{
-			Errors: []string{"incorrect password"},
-			Code:   http.StatusUnauthorized,
-		}, w)
-
-		return
-	}
-
-	var redirect string
-
-	if valR := session.Values["redirect"]; valR != nil {
-		redirect = session.Values["redirect"].(string)
-	}
-
-	// Set user as authenticated
-	session.Values["authenticated"] = true
-	session.Values["user_id"] = storedUser.ID
-	session.Values["email"] = storedUser.Email
-	session.Values["redirect"] = ""
-
-	if err := session.Save(r, w); err != nil {
-		app.Logger.Warn().Err(err)
-	}
-
-	w.WriteHeader(http.StatusOK)
-
-	if err := app.sendUser(w, storedUser.ID, storedUser.Email, storedUser.EmailVerified, redirect); err != nil {
-		app.handleErrorFormDecoding(err, ErrUserDecode, w)
-		return
-	}
-}
-
-// HandleLogoutUser detaches the user from the session
-func (app *App) HandleLogoutUser(w http.ResponseWriter, r *http.Request) {
-	session, err := app.Store.Get(r, app.ServerConf.CookieName)
-
-	if err != nil {
-		app.handleErrorDataRead(err, w)
-	}
-
-	session.Values["authenticated"] = false
-	session.Values["user_id"] = nil
-	session.Values["email"] = nil
-	session.Save(r, w)
-	w.WriteHeader(http.StatusOK)
-}
-
-// HandleReadUser returns an externalized User (models.UserExternal)
-// based on an ID
-func (app *App) HandleReadUser(w http.ResponseWriter, r *http.Request) {
-	user, err := app.readUser(w, r)
-
-	// error already handled by helper
-	if err != nil {
-		return
-	}
-
-	extUser := user.Externalize()
-
-	if err := json.NewEncoder(w).Encode(extUser); err != nil {
-		app.handleErrorFormDecoding(err, ErrUserDecode, w)
-		return
-	}
-
-	w.WriteHeader(http.StatusOK)
-}
-
-// HandleListUserProjects lists all projects belonging to a given user
-func (app *App) HandleListUserProjects(w http.ResponseWriter, r *http.Request) {
-	id, err := strconv.ParseUint(chi.URLParam(r, "user_id"), 0, 64)
-
-	if err != nil || id == 0 {
-		app.handleErrorFormDecoding(err, ErrUserDecode, w)
-		return
-	}
-
-	projects, err := app.Repo.Project().ListProjectsByUserID(uint(id))
-
-	if err != nil {
-		app.handleErrorRead(err, ErrUserDataRead, w)
-	}
-
-	projectsExt := make([]*types.Project, 0)
-
-	for _, project := range projects {
-		projectsExt = append(projectsExt, project.ToProjectType())
-	}
-
-	w.WriteHeader(http.StatusOK)
-
-	if err := json.NewEncoder(w).Encode(projectsExt); err != nil {
-		app.handleErrorFormDecoding(err, ErrUserDecode, w)
-		return
-	}
-}
-
-// HandleDeleteUser removes a user after checking that the sent password is correct
-func (app *App) HandleDeleteUser(w http.ResponseWriter, r *http.Request) {
-	id, err := strconv.ParseUint(chi.URLParam(r, "user_id"), 0, 64)
-
-	if err != nil || id == 0 {
-		app.handleErrorFormDecoding(err, ErrUserDecode, w)
-		return
-	}
-
-	// TODO -- HASH AND VERIFY PASSWORD BEFORE USER DELETION
-	form := &forms.DeleteUserForm{
-		ID: uint(id),
-	}
-
-	user, err := app.writeUser(form, app.Repo.User().DeleteUser, w, r)
-
-	if err == nil {
-		app.Logger.Info().Msgf("User deleted: %d", user.ID)
-		w.WriteHeader(http.StatusNoContent)
-	}
-}
-
-// InitiateEmailVerifyUser initiates the email verification flow for a logged-in user
-func (app *App) InitiateEmailVerifyUser(w http.ResponseWriter, r *http.Request) {
-	userID, err := app.getUserIDFromRequest(r)
-
-	if err != nil {
-		app.handleErrorInternal(err, w)
-		return
-	}
-
-	user, err := app.Repo.User().ReadUser(userID)
-
-	if err != nil {
-		app.handleErrorInternal(err, w)
-		return
-	}
-
-	err = app.startEmailVerificationFlow(user)
-
-	if err != nil {
-		app.handleErrorInternal(err, w)
-		return
-	}
-
-	w.WriteHeader(http.StatusOK)
-}
-
-// FinalizeEmailVerifyUser completes the email verification flow for a user.
-func (app *App) FinalizeEmailVerifyUser(w http.ResponseWriter, r *http.Request) {
-	userID, err := app.getUserIDFromRequest(r)
-
-	if err != nil {
-		app.handleErrorInternal(err, w)
-		return
-	}
-
-	user, err := app.Repo.User().ReadUser(userID)
-
-	if err != nil {
-		app.handleErrorInternal(err, w)
-		return
-	}
-
-	vals, err := url.ParseQuery(r.URL.RawQuery)
-
-	if err != nil {
-		http.Redirect(w, r, "/dashboard?error="+url.QueryEscape("Invalid email verification URL"), 302)
-		return
-	}
-
-	var tokenStr string
-	var tokenID uint
-
-	if tokenArr, ok := vals["token"]; ok && len(tokenArr) == 1 {
-		tokenStr = tokenArr[0]
-	} else {
-		http.Redirect(w, r, "/dashboard?error="+url.QueryEscape("Invalid email verification URL: token required"), 302)
-		return
-	}
-
-	if tokenIDArr, ok := vals["token_id"]; ok && len(tokenIDArr) == 1 {
-		id, err := strconv.ParseUint(tokenIDArr[0], 10, 64)
-
-		if err != nil {
-			http.Redirect(w, r, "/dashboard?error="+url.QueryEscape("Invalid email verification URL: valid token id required"), 302)
-			return
-		}
-
-		tokenID = uint(id)
-	} else {
-		http.Redirect(w, r, "/dashboard?error="+url.QueryEscape("Invalid email verification URL: valid token id required"), 302)
-		return
-	}
-
-	// verify the token is valid
-	token, err := app.Repo.PWResetToken().ReadPWResetToken(tokenID)
-
-	if err != nil {
-		http.Redirect(w, r, "/dashboard?error="+url.QueryEscape("Email verification error: valid token required"), 302)
-		return
-	}
-
-	// make sure the token is still valid and has not expired
-	if !token.IsValid || token.IsExpired() {
-		http.Redirect(w, r, "/dashboard?error="+url.QueryEscape("Email verification error: valid token required"), 302)
-		return
-	}
-
-	// make sure the token is correct
-	if err := bcrypt.CompareHashAndPassword([]byte(token.Token), []byte(tokenStr)); err != nil {
-		http.Redirect(w, r, "/dashboard?error="+url.QueryEscape("Email verification error: valid token required"), 302)
-		return
-	}
-
-	user.EmailVerified = true
-
-	user, err = app.Repo.User().UpdateUser(user)
-
-	if err != nil {
-		http.Redirect(w, r, "/dashboard?error="+url.QueryEscape("Could not verify email address"), 302)
-		return
-	}
-
-	// invalidate the token
-	token.IsValid = false
-
-	_, err = app.Repo.PWResetToken().UpdatePWResetToken(token)
-
-	if err != nil {
-		http.Redirect(w, r, "/dashboard?error="+url.QueryEscape("Could not verify email address"), 302)
-		return
-	}
-
-	app.AnalyticsClient.Track(analytics.UserVerifyEmailTrack(&analytics.UserVerifyEmailTrackOpts{
-		UserScopedTrackOpts: analytics.GetUserScopedTrackOpts(user.ID),
-		Email:               user.Email,
-	}))
-
-	http.Redirect(w, r, "/dashboard", 302)
-	return
-}
-
-// InitiatePWResetUser initiates the password reset flow based on an email. The endpoint
-// checks if the email exists, but returns a 200 status code regardless, since we don't
-// want to leak in-use emails
-func (app *App) InitiatePWResetUser(w http.ResponseWriter, r *http.Request) {
-	form := &forms.InitiateResetUserPasswordForm{}
-
-	// decode from JSON to form value
-	if err := json.NewDecoder(r.Body).Decode(form); err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	// validate the form
-	if err := app.validator.Struct(form); err != nil {
-		app.handleErrorFormValidation(err, ErrProjectValidateFields, w)
-		return
-	}
-
-	// check that the email exists; return 200 status code even if it doesn't
-	user, err := app.Repo.User().ReadUserByEmail(form.Email)
-
-	if err == gorm.ErrRecordNotFound {
-		w.WriteHeader(http.StatusOK)
-		return
-	} else if err != nil {
-		app.handleErrorDataRead(err, w)
-		return
-	}
-
-	// if the user is a Github user, send them a Github email
-	if user.GithubUserID != 0 {
-		err := app.notifier.SendGithubRelinkEmail(
-			&notifier.SendGithubRelinkEmailOpts{
-				Email: user.Email,
-				URL:   fmt.Sprintf("%s/api/oauth/login/github", app.ServerConf.ServerURL),
-			},
-		)
-
-		if err != nil {
-			app.handleErrorInternal(err, w)
-			return
-		}
-
-		w.WriteHeader(http.StatusOK)
-		return
-	}
-
-	// convert the form to a project model
-	pwReset, rawToken, err := form.ToPWResetToken()
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	// handle write to the database
-	pwReset, err = app.Repo.PWResetToken().CreatePWResetToken(pwReset)
-
-	if err != nil {
-		app.handleErrorDataWrite(err, w)
-		return
-	}
-
-	queryVals := url.Values{
-		"token":    []string{rawToken},
-		"email":    []string{form.Email},
-		"token_id": []string{fmt.Sprintf("%d", pwReset.ID)},
-	}
-
-	err = app.notifier.SendPasswordResetEmail(
-		&notifier.SendPasswordResetEmailOpts{
-			Email: user.Email,
-			URL:   fmt.Sprintf("%s/password/reset/finalize?%s", app.ServerConf.ServerURL, queryVals.Encode()),
-		},
-	)
-
-	if err != nil {
-		app.handleErrorInternal(err, w)
-		return
-	}
-
-	w.WriteHeader(http.StatusOK)
-	return
-}
-
-// VerifyPWResetUser makes sure that the token is correct and still valid
-func (app *App) VerifyPWResetUser(w http.ResponseWriter, r *http.Request) {
-	form := &forms.VerifyResetUserPasswordForm{}
-
-	// decode from JSON to form value
-	if err := json.NewDecoder(r.Body).Decode(form); err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	// validate the form
-	if err := app.validator.Struct(form); err != nil {
-		app.handleErrorFormValidation(err, ErrProjectValidateFields, w)
-		return
-	}
-
-	token, err := app.Repo.PWResetToken().ReadPWResetToken(form.PWResetTokenID)
-
-	if err != nil {
-		w.WriteHeader(http.StatusForbidden)
-		return
-	}
-
-	// make sure the token is still valid and has not expired
-	if !token.IsValid || token.IsExpired() {
-		w.WriteHeader(http.StatusForbidden)
-		return
-	}
-
-	// check that the email matches
-	if token.Email != form.Email {
-		w.WriteHeader(http.StatusForbidden)
-		return
-	}
-
-	// make sure the token is correct
-	if err := bcrypt.CompareHashAndPassword([]byte(token.Token), []byte(form.Token)); err != nil {
-		w.WriteHeader(http.StatusForbidden)
-		return
-	}
-
-	w.WriteHeader(http.StatusOK)
-	return
-}
-
-// FinalizPWResetUser completes the password reset flow based on an email.
-func (app *App) FinalizPWResetUser(w http.ResponseWriter, r *http.Request) {
-	form := &forms.FinalizeResetUserPasswordForm{}
-
-	// decode from JSON to form value
-	if err := json.NewDecoder(r.Body).Decode(form); err != nil {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	// validate the form
-	if err := app.validator.Struct(form); err != nil {
-		app.handleErrorFormValidation(err, ErrProjectValidateFields, w)
-		return
-	}
-
-	// verify the token is valid
-	token, err := app.Repo.PWResetToken().ReadPWResetToken(form.PWResetTokenID)
-
-	if err != nil {
-		w.WriteHeader(http.StatusForbidden)
-		return
-	}
-
-	// make sure the token is still valid and has not expired
-	if !token.IsValid || token.IsExpired() {
-		w.WriteHeader(http.StatusForbidden)
-		return
-	}
-
-	// check that the email matches
-	if token.Email != form.Email {
-		w.WriteHeader(http.StatusForbidden)
-		return
-	}
-
-	// make sure the token is correct
-	if err := bcrypt.CompareHashAndPassword([]byte(token.Token), []byte(form.Token)); err != nil {
-		w.WriteHeader(http.StatusForbidden)
-		return
-	}
-
-	// check that the email exists
-	user, err := app.Repo.User().ReadUserByEmail(form.Email)
-
-	if err != nil {
-		w.WriteHeader(http.StatusForbidden)
-		return
-	}
-
-	hashedPW, err := bcrypt.GenerateFromPassword([]byte(form.NewPassword), 8)
-
-	if err != nil {
-		app.handleErrorDataWrite(err, w)
-		return
-	}
-
-	user.Password = string(hashedPW)
-
-	user, err = app.Repo.User().UpdateUser(user)
-
-	if err != nil {
-		app.handleErrorDataWrite(err, w)
-		return
-	}
-
-	// invalidate the token
-	token.IsValid = false
-
-	_, err = app.Repo.PWResetToken().UpdatePWResetToken(token)
-
-	if err != nil {
-		app.handleErrorDataWrite(err, w)
-		return
-	}
-
-	w.WriteHeader(http.StatusOK)
-	return
-}
-
-// ------------------------ User handler helper functions ------------------------ //
-
-// writeUser will take a POST or PUT request to the /api/users endpoint and decode
-// the request into a forms.WriteUserForm model, convert it to a models.User, and
-// write to the database.
-func (app *App) writeUser(
-	form forms.WriteUserForm,
-	dbWrite repository.WriteUser,
-	w http.ResponseWriter,
-	r *http.Request,
-	validators ...func(repo repository.Repository, user *models.User) *HTTPError,
-) (*models.User, error) {
-	// decode from JSON to form value
-	if err := json.NewDecoder(r.Body).Decode(form); err != nil {
-		app.handleErrorFormDecoding(err, ErrUserDecode, w)
-		return nil, err
-	}
-
-	// validate the form
-	if err := app.validator.Struct(form); err != nil {
-		app.handleErrorFormValidation(err, ErrUserValidateFields, w)
-		return nil, err
-	}
-
-	// convert the form to a user model -- WriteUserForm must implement ToUser
-	userModel, err := form.ToUser(app.Repo.User())
-
-	if err != nil {
-		app.handleErrorFormDecoding(err, ErrUserDecode, w)
-		return nil, err
-	}
-
-	// Check any additional validators for any semantic errors
-	// We have completed all syntax checks, so these will be sent
-	// with http.StatusUnprocessableEntity (422), unless this is
-	// an internal server error
-	for _, validator := range validators {
-		err := validator(app.Repo, userModel)
-
-		if err != nil {
-			goErr := errors.New(strings.Join(err.Errors, ", "))
-			if err.Code == 500 {
-				app.sendExternalError(
-					goErr,
-					http.StatusInternalServerError,
-					*err,
-					w,
-				)
-			} else {
-				app.sendExternalError(
-					goErr,
-					http.StatusUnprocessableEntity,
-					*err,
-					w,
-				)
-			}
-
-			return nil, goErr
-		}
-	}
-
-	// handle write to the database
-	user, err := dbWrite(userModel)
-
-	if err != nil {
-		app.handleErrorDataWrite(err, w)
-		return nil, err
-	}
-
-	return user, nil
-}
-
-func (app *App) readUser(w http.ResponseWriter, r *http.Request) (*models.User, error) {
-	id, err := strconv.ParseUint(chi.URLParam(r, "user_id"), 0, 64)
-
-	if err != nil || id == 0 {
-		app.handleErrorFormDecoding(err, ErrUserDecode, w)
-		return nil, err
-	}
-
-	user, err := app.Repo.User().ReadUser(uint(id))
-
-	if err != nil {
-		app.handleErrorRead(err, ErrUserDataRead, w)
-		return nil, err
-	}
-
-	return user, nil
-}
-
-func doesUserExist(repo repository.Repository, user *models.User) *HTTPError {
-	user, err := repo.User().ReadUserByEmail(user.Email)
-
-	if user != nil && err == nil {
-		return &HTTPError{
-			Code: ErrUserValidateFields,
-			Errors: []string{
-				"email already taken",
-			},
-		}
-	}
-
-	if err != gorm.ErrRecordNotFound {
-		return &ErrorDataRead
-	}
-
-	return nil
-}
-
-type SendUserExt struct {
-	ID            uint   `json:"id"`
-	Email         string `json:"email"`
-	EmailVerified bool   `json:"email_verified"`
-	Redirect      string `json:"redirect,omitempty"`
-}
-
-func (app *App) sendUser(w http.ResponseWriter, userID uint, email string, emailVerified bool, redirect string) error {
-	resUser := &SendUserExt{
-		ID:            userID,
-		Email:         email,
-		EmailVerified: emailVerified,
-		Redirect:      redirect,
-	}
-
-	if err := json.NewEncoder(w).Encode(resUser); err != nil {
-		return err
-	}
-	return nil
-}
-
-func (app *App) getUserIDFromRequest(r *http.Request) (uint, error) {
-	// first, check for token
-	tok := app.getTokenFromRequest(r)
-
-	if tok != nil {
-		return tok.IBy, nil
-	}
-
-	session, err := app.Store.Get(r, app.ServerConf.CookieName)
-
-	if err != nil {
-		return 0, fmt.Errorf("could not get session: %s", err.Error())
-	}
-
-	sessID, ok := session.Values["user_id"]
-
-	if !ok {
-		return 0, fmt.Errorf("could not get user id from session")
-	}
-
-	userID, ok := sessID.(uint)
-
-	if !ok {
-		return 0, fmt.Errorf("could not get user id from session")
-	}
-
-	return userID, nil
-}
-
-func (app *App) startEmailVerificationFlow(user *models.User) error {
-	form := &forms.InitiateResetUserPasswordForm{
-		Email: user.Email,
-	}
-
-	// convert the form to a pw reset token model
-	pwReset, rawToken, err := form.ToPWResetToken()
-
-	if err != nil {
-		return err
-	}
-
-	// handle write to the database
-	pwReset, err = app.Repo.PWResetToken().CreatePWResetToken(pwReset)
-
-	if err != nil {
-		return err
-	}
-
-	queryVals := url.Values{
-		"token":    []string{rawToken},
-		"token_id": []string{fmt.Sprintf("%d", pwReset.ID)},
-	}
-
-	return app.notifier.SendEmailVerification(
-		&notifier.SendEmailVerificationOpts{
-			Email: form.Email,
-			URL:   fmt.Sprintf("%s/api/email/verify/finalize?%s", app.ServerConf.ServerURL, queryVals.Encode()),
-		},
-	)
-}

+ 0 - 575
server/api/user_handler_test.go

@@ -1,575 +0,0 @@
-package api_test
-
-// import (
-// 	"encoding/json"
-// 	"fmt"
-// 	"net/http"
-// 	"net/http/httptest"
-// 	"reflect"
-// 	"strings"
-// 	"testing"
-
-// 	"github.com/go-test/deep"
-// 	"github.com/porter-dev/porter/api/types"
-// 	"github.com/porter-dev/porter/internal/auth/token"
-// 	"github.com/porter-dev/porter/internal/models"
-// )
-
-// // ------------------------- TEST TYPES AND MAIN LOOP ------------------------- //
-
-// type userTest struct {
-// 	initializers []func(t *tester)
-// 	msg          string
-// 	method       string
-// 	endpoint     string
-// 	body         string
-// 	expStatus    int
-// 	expBody      string
-// 	useCookie    bool
-// 	validators   []func(c *userTest, tester *tester, t *testing.T)
-// }
-
-// func testUserRequests(t *testing.T, tests []*userTest, canQuery bool) {
-// 	for _, c := range tests {
-// 		// create a new tester
-// 		tester := newTester(canQuery)
-
-// 		// if there's an initializer, call it
-// 		for _, init := range c.initializers {
-// 			init(tester)
-// 		}
-
-// 		req, err := http.NewRequest(
-// 			c.method,
-// 			c.endpoint,
-// 			strings.NewReader(c.body),
-// 		)
-
-// 		tester.req = req
-
-// 		if c.useCookie {
-// 			req.AddCookie(tester.cookie)
-// 		}
-
-// 		if err != nil {
-// 			t.Fatal(err)
-// 		}
-
-// 		tester.execute()
-// 		rr := tester.rr
-
-// 		// first, check that the status matches
-// 		if status := rr.Code; status != c.expStatus {
-// 			t.Errorf("%s, handler returned wrong status code: got %v want %v",
-// 				c.msg, status, c.expStatus)
-// 		}
-
-// 		// if there's a validator, call it
-// 		for _, validate := range c.validators {
-// 			validate(c, tester, t)
-// 		}
-// 	}
-// }
-
-// // ------------------------- TEST FIXTURES AND FUNCTIONS  ------------------------- //
-
-// var authCheckTests = []*userTest{
-// 	{
-// 		initializers: []func(tester *tester){
-// 			initUserDefault,
-// 		},
-// 		msg:       "Auth check successful. User is logged in.",
-// 		method:    "GET",
-// 		endpoint:  "/api/auth/check",
-// 		expStatus: http.StatusOK,
-// 		body:      "",
-// 		expBody:   `{"id":1,"email":"belanger@getporter.dev","email_verified":false}`,
-// 		useCookie: true,
-// 		validators: []func(c *userTest, tester *tester, t *testing.T){
-// 			userBasicBodyValidator,
-// 		},
-// 	},
-// 	{
-// 		initializers: []func(tester *tester){
-// 			initUserDefault,
-// 		},
-// 		msg:       "Auth check failure. User is not logged in.",
-// 		method:    "GET",
-// 		endpoint:  "/api/auth/check",
-// 		body:      "",
-// 		expStatus: http.StatusForbidden,
-// 		expBody:   http.StatusText(http.StatusForbidden) + "\n",
-// 		validators: []func(c *userTest, tester *tester, t *testing.T){
-// 			userBasicBodyValidator,
-// 		},
-// 	},
-// }
-
-// func TestHandleAuthCheck(t *testing.T) {
-// 	testUserRequests(t, authCheckTests, true)
-// }
-
-// func TestHandleAuthCheckToken(t *testing.T) {
-// 	tester := newTester(true)
-
-// 	initUserDefault(tester)
-// 	initProject(tester)
-
-// 	// generate a new token
-// 	tokGen, _ := token.GetTokenForAPI(1, 1)
-
-// 	tok, _ := tokGen.EncodeToken(&token.TokenGeneratorConf{
-// 		TokenSecret: "secret",
-// 	})
-
-// 	req, err := http.NewRequest(
-// 		"GET",
-// 		"/api/auth/check",
-// 		strings.NewReader(""),
-// 	)
-
-// 	tester.req = req
-// 	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", tok))
-// 	tester.execute()
-
-// 	rr := tester.rr
-
-// 	if err != nil {
-// 		t.Fatal(err)
-// 	}
-
-// 	// first, check that the status matches
-// 	if status := rr.Code; status != 200 {
-// 		t.Errorf("%s, handler returned wrong status code: got %v want %v",
-// 			"auth check token", status, 200)
-// 	}
-
-// 	gotBody := &models.UserExternal{}
-// 	expBody := &models.UserExternal{}
-
-// 	json.Unmarshal(tester.rr.Body.Bytes(), gotBody)
-// 	json.Unmarshal([]byte(`{"id":1,"email":"belanger@getporter.dev"}`), expBody)
-
-// 	if !reflect.DeepEqual(gotBody, expBody) {
-// 		t.Errorf("%s, handler returned wrong body: got %v want %v",
-// 			"auth check token", gotBody, expBody)
-// 	}
-// }
-
-// var createUserTests = []*userTest{
-// 	{
-// 		msg:      "Create user",
-// 		method:   "POST",
-// 		endpoint: "/api/users",
-// 		body: `{
-// 			"email": "belanger@getporter.dev",
-// 			"password": "hello"
-// 		}`,
-// 		expStatus: http.StatusCreated,
-// 		expBody:   `{"id":1,"email":"belanger@getporter.dev"}`,
-// 		validators: []func(c *userTest, tester *tester, t *testing.T){
-// 			userModelBodyValidator,
-// 		},
-// 	},
-// 	{
-// 		msg:      "Create user invalid email",
-// 		method:   "POST",
-// 		endpoint: "/api/users",
-// 		body: `{
-// 			"email": "notanemail",
-// 			"password": "hello"
-// 		}`,
-// 		expStatus: http.StatusUnprocessableEntity,
-// 		expBody:   `{"code":601,"errors":["email validation failed"]}`,
-// 		validators: []func(c *userTest, tester *tester, t *testing.T){
-// 			userBasicBodyValidator,
-// 		},
-// 	},
-// 	{
-// 		msg:      "Create user missing field",
-// 		method:   "POST",
-// 		endpoint: "/api/users",
-// 		body: `{
-// 			"password": "hello"
-// 		}`,
-// 		expStatus: http.StatusUnprocessableEntity,
-// 		expBody:   `{"code":601,"errors":["required validation failed"]}`,
-// 		validators: []func(c *userTest, tester *tester, t *testing.T){
-// 			userBasicBodyValidator,
-// 		},
-// 	},
-// 	{
-// 		initializers: []func(tester *tester){
-// 			initUserDefault,
-// 		},
-// 		msg:      "Create user same email",
-// 		method:   "POST",
-// 		endpoint: "/api/users",
-// 		body: `{
-// 			"email": "belanger@getporter.dev",
-// 			"password": "hello"
-// 		}`,
-// 		expStatus: http.StatusUnprocessableEntity,
-// 		expBody:   `{"code":601,"errors":["email already taken"]}`,
-// 		validators: []func(c *userTest, tester *tester, t *testing.T){
-// 			userBasicBodyValidator,
-// 		},
-// 	},
-// 	{
-// 		msg:      "Create user invalid field type",
-// 		method:   "POST",
-// 		endpoint: "/api/users",
-// 		body: `{
-// 			"email": "belanger@getporter.dev",
-// 			"password": 0
-// 		}`,
-// 		expStatus: http.StatusBadRequest,
-// 		expBody:   `{"code":600,"errors":["could not process request"]}`,
-// 		validators: []func(c *userTest, tester *tester, t *testing.T){
-// 			userBasicBodyValidator,
-// 		},
-// 	},
-// }
-
-// func TestHandleCreateUser(t *testing.T) {
-// 	testUserRequests(t, createUserTests, true)
-// }
-
-// var createUserTestsWriteFail = []*userTest{
-// 	{
-// 		msg:      "Create user db connection down",
-// 		method:   "POST",
-// 		endpoint: "/api/users",
-// 		body: `{
-// 		"email": "belanger@getporter.dev",
-// 		"password": "hello"
-// 	}`,
-// 		expStatus: http.StatusInternalServerError,
-// 		expBody:   `{"code":500,"errors":["could not read from database"]}`,
-// 		validators: []func(c *userTest, tester *tester, t *testing.T){
-// 			userBasicBodyValidator,
-// 		},
-// 	},
-// }
-
-// func TestHandleCreateUserWriteFail(t *testing.T) {
-// 	testUserRequests(t, createUserTestsWriteFail, false)
-// }
-
-// var loginUserTests = []*userTest{
-// 	{
-// 		initializers: []func(tester *tester){
-// 			initUserDefault,
-// 		},
-// 		msg:      "Login user successful",
-// 		method:   "POST",
-// 		endpoint: "/api/login",
-// 		body: `{
-// 			"email": "belanger@getporter.dev",
-// 			"password": "hello"
-// 		}`,
-// 		expStatus: http.StatusOK,
-// 		expBody:   `{"id":1,"email":"belanger@getporter.dev","email_verified":false}`,
-// 		validators: []func(c *userTest, tester *tester, t *testing.T){
-// 			userBasicBodyValidator,
-// 		},
-// 	},
-// 	{
-// 		initializers: []func(tester *tester){
-// 			initUserDefault,
-// 		},
-// 		msg:      "Login user already logged in",
-// 		method:   "POST",
-// 		endpoint: "/api/login",
-// 		body: `{
-// 			"email": "belanger@getporter.dev",
-// 			"password": "hello"
-// 		}`,
-// 		expStatus: http.StatusOK,
-// 		expBody:   `{"id":1,"email":"belanger@getporter.dev","email_verified":false}`,
-// 		useCookie: true,
-// 		validators: []func(c *userTest, tester *tester, t *testing.T){
-// 			userBasicBodyValidator,
-// 		},
-// 	},
-// 	{
-// 		msg:      "Login user unregistered email",
-// 		method:   "POST",
-// 		endpoint: "/api/login",
-// 		body: `{
-// 			"email": "belanger@getporter.dev",
-// 			"password": "hello"
-// 		}`,
-// 		expStatus: http.StatusUnauthorized,
-// 		expBody:   `{"code":401,"errors":["email not registered"]}`,
-// 		validators: []func(c *userTest, tester *tester, t *testing.T){
-// 			userBasicBodyValidator,
-// 		},
-// 	},
-// 	{
-// 		initializers: []func(tester *tester){
-// 			initUserDefault,
-// 		},
-// 		msg:      "Login user incorrect password",
-// 		method:   "POST",
-// 		endpoint: "/api/login",
-// 		body: `{
-// 			"email": "belanger@getporter.dev",
-// 			"password": "notthepassword"
-// 		}`,
-// 		expStatus: http.StatusUnauthorized,
-// 		expBody:   `{"code":401,"errors":["incorrect password"]}`,
-// 		useCookie: true,
-// 		validators: []func(c *userTest, tester *tester, t *testing.T){
-// 			userBasicBodyValidator,
-// 		},
-// 	},
-// }
-
-// func TestHandleLoginUser(t *testing.T) {
-// 	testUserRequests(t, loginUserTests, true)
-// }
-
-// var logoutUserTests = []*userTest{
-// 	{
-// 		initializers: []func(tester *tester){
-// 			initUserDefault,
-// 		},
-// 		msg:      "Logout user successful",
-// 		method:   "POST",
-// 		endpoint: "/api/logout",
-// 		body: `{
-// 			"email": "belanger@getporter.dev",
-// 			"password": "hello"
-// 		}`,
-// 		expStatus: http.StatusOK,
-// 		expBody:   ``,
-// 		useCookie: true,
-// 		validators: []func(c *userTest, tester *tester, t *testing.T){
-// 			func(c *userTest, tester *tester, t *testing.T) {
-// 				req, err := http.NewRequest(
-// 					"GET",
-// 					"/api/users/1",
-// 					strings.NewReader(""),
-// 				)
-
-// 				req.AddCookie(tester.cookie)
-
-// 				if err != nil {
-// 					t.Fatal(err)
-// 				}
-
-// 				rr2 := httptest.NewRecorder()
-// 				tester.router.ServeHTTP(rr2, req)
-
-// 				if status := rr2.Code; status != http.StatusForbidden {
-// 					t.Errorf("%s, handler returned wrong status: got %v want %v",
-// 						"validator failed", status, http.StatusForbidden)
-// 				}
-// 			},
-// 		},
-// 	},
-// }
-
-// func TestHandleLogoutUser(t *testing.T) {
-// 	testUserRequests(t, logoutUserTests, true)
-// }
-
-// var readUserTests = []*userTest{
-// 	{
-// 		initializers: []func(tester *tester){
-// 			initUserDefault,
-// 		},
-// 		msg:       "Read user successful",
-// 		method:    "GET",
-// 		endpoint:  "/api/users/1",
-// 		body:      "",
-// 		expStatus: http.StatusOK,
-// 		expBody:   `{"id":1,"email":"belanger@getporter.dev"}`,
-// 		useCookie: true,
-// 		validators: []func(c *userTest, tester *tester, t *testing.T){
-// 			userModelBodyValidator,
-// 		},
-// 	},
-// 	{
-// 		initializers: []func(tester *tester){
-// 			initUserDefault,
-// 		},
-// 		msg:       "Read user unauthorized",
-// 		method:    "GET",
-// 		endpoint:  "/api/users/2",
-// 		body:      "",
-// 		expStatus: http.StatusForbidden,
-// 		expBody:   http.StatusText(http.StatusForbidden) + "\n",
-// 		validators: []func(c *userTest, tester *tester, t *testing.T){
-// 			userBasicBodyValidator,
-// 		},
-// 	},
-// }
-
-// func TestHandleReadUser(t *testing.T) {
-// 	testUserRequests(t, readUserTests, true)
-// }
-
-// var listUserProjectsTests = []*userTest{
-// 	{
-// 		initializers: []func(tester *tester){
-// 			initUserDefault,
-// 			initProject,
-// 		},
-// 		msg:       "List user projects successful",
-// 		method:    "GET",
-// 		endpoint:  "/api/users/1/projects",
-// 		body:      "",
-// 		expStatus: http.StatusOK,
-// 		expBody:   `[{"id":1,"name":"project-test","roles":[{"id":0,"kind":"admin","user_id":1,"project_id":1}]}]`,
-// 		useCookie: true,
-// 		validators: []func(c *userTest, tester *tester, t *testing.T){
-// 			userProjectsListValidator,
-// 		},
-// 	},
-// 	{
-// 		initializers: []func(tester *tester){
-// 			initUserDefault,
-// 		},
-// 		msg:       "List user projects unauthorized",
-// 		method:    "GET",
-// 		endpoint:  "/api/users/2/projects",
-// 		body:      "",
-// 		expStatus: http.StatusForbidden,
-// 		expBody:   http.StatusText(http.StatusForbidden) + "\n",
-// 		validators: []func(c *userTest, tester *tester, t *testing.T){
-// 			userBasicBodyValidator,
-// 		},
-// 	},
-// }
-
-// func TestHandleListUserProjects(t *testing.T) {
-// 	testUserRequests(t, listUserProjectsTests, true)
-// }
-
-// var deleteUserTests = []*userTest{
-// 	{
-// 		initializers: []func(tester *tester){
-// 			initUserDefault,
-// 		},
-// 		msg:       "Delete user successful",
-// 		method:    "DELETE",
-// 		endpoint:  "/api/users/1",
-// 		body:      `{"password":"hello"}`,
-// 		expStatus: http.StatusNoContent,
-// 		expBody:   "",
-// 		useCookie: true,
-// 		validators: []func(c *userTest, tester *tester, t *testing.T){
-// 			func(c *userTest, tester *tester, t *testing.T) {
-// 				req, err := http.NewRequest(
-// 					"GET",
-// 					"/api/users/1",
-// 					strings.NewReader(""),
-// 				)
-
-// 				req.AddCookie(tester.cookie)
-
-// 				if err != nil {
-// 					t.Fatal(err)
-// 				}
-
-// 				rr2 := httptest.NewRecorder()
-
-// 				tester.router.ServeHTTP(rr2, req)
-
-// 				gotBody := &models.UserExternal{}
-// 				expBody := &models.UserExternal{}
-
-// 				if status := rr2.Code; status != 404 {
-// 					t.Errorf("DELETE user validation, handler returned wrong status code: got %v want %v",
-// 						status, 404)
-// 				}
-
-// 				json.Unmarshal(rr2.Body.Bytes(), gotBody)
-// 				json.Unmarshal([]byte(`{"code":602,"errors":["could not find requested object"]}`), expBody)
-
-// 				if !reflect.DeepEqual(gotBody, expBody) {
-// 					t.Errorf("%s, handler returned wrong body: got %v want %v",
-// 						"validator failed", gotBody, expBody)
-// 				}
-// 			},
-// 		},
-// 	},
-// 	{
-// 		initializers: []func(tester *tester){
-// 			initUserDefault,
-// 		},
-// 		msg:       "Delete user invalid id",
-// 		method:    "DELETE",
-// 		endpoint:  "/api/users/aldkjf",
-// 		body:      `{"password":"hello"}`,
-// 		expStatus: http.StatusForbidden,
-// 		expBody:   http.StatusText(http.StatusForbidden) + "\n",
-// 		validators: []func(c *userTest, tester *tester, t *testing.T){
-// 			userBasicBodyValidator,
-// 		},
-// 	},
-// 	{
-// 		initializers: []func(tester *tester){
-// 			initUserDefault,
-// 		},
-// 		msg:       "Delete user missing password",
-// 		method:    "DELETE",
-// 		endpoint:  "/api/users/1",
-// 		body:      `{}`,
-// 		expStatus: http.StatusUnprocessableEntity,
-// 		expBody:   `{"code":601,"errors":["required validation failed"]}`,
-// 		useCookie: true,
-// 		validators: []func(c *userTest, tester *tester, t *testing.T){
-// 			userBasicBodyValidator,
-// 		},
-// 	},
-// }
-
-// func TestHandleDeleteUser(t *testing.T) {
-// 	testUserRequests(t, deleteUserTests, true)
-// }
-
-// // ------------------------- INITIALIZERS AND VALIDATORS ------------------------- //
-
-// func initUserDefault(tester *tester) {
-// 	tester.createUserSession("belanger@getporter.dev", "hello")
-// }
-
-// func initUserAlt(tester *tester) {
-// 	tester.createUserSession("test@test.it", "hello")
-// }
-
-// func userBasicBodyValidator(c *userTest, tester *tester, t *testing.T) {
-// 	if body := tester.rr.Body.String(); strings.TrimSpace(body) != strings.TrimSpace(c.expBody) {
-// 		t.Errorf("%s, handler returned wrong body: got %v want %v",
-// 			c.msg, body, c.expBody)
-// 	}
-// }
-
-// func userModelBodyValidator(c *userTest, tester *tester, t *testing.T) {
-// 	gotBody := &models.UserExternal{}
-// 	expBody := &models.UserExternal{}
-
-// 	json.Unmarshal(tester.rr.Body.Bytes(), gotBody)
-// 	json.Unmarshal([]byte(c.expBody), expBody)
-
-// 	if !reflect.DeepEqual(gotBody, expBody) {
-// 		t.Errorf("%s, handler returned wrong body: got %v want %v",
-// 			c.msg, gotBody, expBody)
-// 	}
-// }
-
-// func userProjectsListValidator(c *userTest, tester *tester, t *testing.T) {
-// 	gotBody := make([]*types.Project, 0)
-// 	expBody := make([]*types.Project, 0)
-
-// 	json.Unmarshal(tester.rr.Body.Bytes(), &gotBody)
-// 	json.Unmarshal([]byte(c.expBody), &expBody)
-
-// 	if diff := deep.Equal(gotBody, expBody); diff != nil {
-// 		t.Errorf("handler returned wrong body:\n")
-// 		t.Error(diff)
-// 	}
-// }

+ 0 - 34
server/api/welcome_handler.go

@@ -1,34 +0,0 @@
-package api
-
-import (
-	"net/http"
-	"net/url"
-)
-
-// HandleGetCapabilities gets the capabilities of the server
-func (app *App) HandleWelcome(w http.ResponseWriter, r *http.Request) {
-	vals, err := url.ParseQuery(r.URL.RawQuery)
-
-	if err != nil {
-		return
-	}
-
-	req, err := http.NewRequest("GET", app.ServerConf.WelcomeFormWebhook, nil)
-
-	if err != nil {
-		return
-	}
-
-	q := req.URL.Query()
-	q.Add("email", vals["email"][0])
-	q.Add("isCompany", vals["isCompany"][0])
-	q.Add("company", vals["company"][0])
-	q.Add("role", vals["role"][0])
-	req.URL.RawQuery = q.Encode()
-
-	_, err = http.Get(req.URL.String())
-
-	if err != nil {
-		return
-	}
-}

+ 0 - 1262
server/middleware/auth.go

@@ -1,1262 +0,0 @@
-package middleware
-
-import (
-	"bytes"
-	"context"
-	"encoding/json"
-	"errors"
-	"io/ioutil"
-	"net/http"
-	"net/url"
-	"strconv"
-	"strings"
-
-	"github.com/google/go-github/github"
-	"github.com/porter-dev/porter/api/types"
-	"github.com/porter-dev/porter/internal/oauth"
-	"golang.org/x/oauth2"
-
-	"github.com/go-chi/chi"
-	"github.com/gorilla/sessions"
-	"github.com/porter-dev/porter/internal/auth/token"
-	"github.com/porter-dev/porter/internal/repository"
-)
-
-// Auth implements the authorization functions
-type Auth struct {
-	store         sessions.Store
-	cookieName    string
-	tokenConf     *token.TokenGeneratorConf
-	repo          repository.Repository
-	GithubAppConf *oauth2.Config
-}
-
-// NewAuth returns a new Auth instance
-func NewAuth(
-	store sessions.Store,
-	cookieName string,
-	tokenConf *token.TokenGeneratorConf,
-	repo repository.Repository,
-	GithubAppConf *oauth2.Config,
-) *Auth {
-	return &Auth{store, cookieName, tokenConf, repo, GithubAppConf}
-}
-
-// BasicAuthenticate just checks that a user is logged in
-func (auth *Auth) BasicAuthenticate(next http.Handler) http.Handler {
-	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		if auth.isLoggedIn(w, r) {
-			next.ServeHTTP(w, r)
-		} else {
-			http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
-			return
-		}
-
-		return
-	})
-}
-
-// BasicAuthenticateWithRedirect checks that a user is logged in, and if they're not, the
-// user is redirected to the login page with the redirect path stored in the session
-func (auth *Auth) BasicAuthenticateWithRedirect(next http.Handler) http.Handler {
-	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		if auth.isLoggedIn(w, r) {
-			next.ServeHTTP(w, r)
-		} else {
-			session, err := auth.store.Get(r, auth.cookieName)
-
-			if err != nil {
-				http.Redirect(w, r, "/dashboard", 302)
-			}
-
-			// need state parameter to validate when redirected
-			if r.URL.RawQuery == "" {
-				session.Values["redirect"] = r.URL.Path
-			} else {
-				session.Values["redirect"] = r.URL.Path + "?" + r.URL.RawQuery
-			}
-
-			session.Save(r, w)
-
-			http.Redirect(w, r, "/dashboard", 302)
-			return
-		}
-
-		return
-	})
-}
-
-// IDLocation represents the location of the ID to use for authentication
-type IDLocation uint
-
-const (
-	// URLParam location looks for a parameter in the URL endpoint
-	URLParam IDLocation = iota
-	// BodyParam location looks for a parameter in the body
-	BodyParam
-	// QueryParam location looks for a parameter in the query string
-	QueryParam
-)
-
-type bodyUserID struct {
-	UserID uint64 `json:"user_id"`
-}
-
-type bodyProjectID struct {
-	ProjectID uint64 `json:"project_id"`
-}
-
-type bodyClusterID struct {
-	ClusterID uint64 `json:"cluster_id"`
-}
-
-type bodyRegistryID struct {
-	RegistryID uint64 `json:"registry_id"`
-}
-
-type bodyGitRepoID struct {
-	GitRepoID uint64 `json:"git_repo_id"`
-}
-
-type bodyInfraID struct {
-	InfraID uint64 `json:"infra_id"`
-}
-
-type bodyInviteID struct {
-	InviteID uint64 `json:"invite_id"`
-}
-
-type bodyAWSIntegrationID struct {
-	AWSIntegrationID uint64 `json:"aws_integration_id"`
-}
-
-type bodyGCPIntegrationID struct {
-	GCPIntegrationID uint64 `json:"gcp_integration_id"`
-}
-
-type bodyDOIntegrationID struct {
-	DOIntegrationID uint64 `json:"do_integration_id"`
-}
-
-// DoesUserIDMatch checks the id URL parameter and verifies that it matches
-// the one stored in the session
-func (auth *Auth) DoesUserIDMatch(next http.Handler, loc IDLocation) http.Handler {
-	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		var err error
-		id, err := findUserIDInRequest(r, loc)
-
-		// first check for token
-		tok := auth.getTokenFromRequest(r)
-
-		if err != nil {
-			http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
-			return
-		} else if tok != nil && tok.IBy == uint(id) {
-			next.ServeHTTP(w, r)
-			return
-		} else if auth.doesSessionMatchID(r, uint(id)) {
-			next.ServeHTTP(w, r)
-		} else {
-			http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
-			return
-		}
-
-		return
-	})
-}
-
-// AccessType represents the various access types for a project
-type AccessType string
-
-// The various access types
-const (
-	AdminAccess AccessType = "admin"
-	ReadAccess  AccessType = "read"
-	WriteAccess AccessType = "write"
-)
-
-// DoesUserHaveProjectAccess looks for a project_id parameter and checks that the
-// user has access via the specified accessType
-func (auth *Auth) DoesUserHaveProjectAccess(
-	next http.Handler,
-	projLoc IDLocation,
-	accessType AccessType,
-) http.Handler {
-	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		var err error
-		projID, err := findProjIDInRequest(r, projLoc)
-
-		if err != nil {
-			http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
-			return
-		}
-
-		// first check for token
-		tok := auth.getTokenFromRequest(r)
-
-		var userID uint
-
-		if tok != nil && tok.ProjectID != 0 && tok.ProjectID == uint(projID) {
-			next.ServeHTTP(w, r)
-			return
-		} else if tok != nil {
-			userID = tok.IBy
-		} else {
-			session, err := auth.store.Get(r, auth.cookieName)
-
-			if err != nil {
-				http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
-				return
-			}
-
-			sessionUserID, ok := session.Values["user_id"]
-
-			if !ok {
-				http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
-				return
-			}
-
-			userID, ok = sessionUserID.(uint)
-
-			if !ok {
-				http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
-				return
-			}
-		}
-
-		// read the user and make sure the email is verified
-		user, err := auth.repo.User().ReadUser(userID)
-
-		if err != nil || !user.EmailVerified {
-			http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
-			return
-		}
-
-		// get the project
-		proj, err := auth.repo.Project().ReadProject(uint(projID))
-
-		if err != nil {
-			http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
-			return
-		}
-
-		// look for the user role in the project
-		for _, role := range proj.Roles {
-			if role.UserID == userID {
-				if accessType == AdminAccess {
-					if role.Kind == types.RoleAdmin {
-						next.ServeHTTP(w, r)
-						return
-					}
-				} else if accessType == WriteAccess {
-					if role.Kind == types.RoleAdmin || role.Kind == types.RoleDeveloper {
-						next.ServeHTTP(w, r)
-						return
-					}
-				} else if accessType == ReadAccess {
-					next.ServeHTTP(w, r)
-					return
-				}
-			}
-		}
-
-		http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
-		return
-	})
-}
-
-// DoesUserHaveClusterAccess looks for a project_id parameter and a
-// cluster_id parameter, and verifies that the cluster belongs
-// to the project
-func (auth *Auth) DoesUserHaveClusterAccess(
-	next http.Handler,
-	projLoc IDLocation,
-	clusterLoc IDLocation,
-) http.Handler {
-	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		clusterID, err := findClusterIDInRequest(r, clusterLoc)
-
-		if err != nil {
-			http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
-			return
-		}
-
-		projID, err := findProjIDInRequest(r, projLoc)
-
-		if err != nil {
-			http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
-			return
-		}
-
-		// get the service accounts belonging to the project
-		clusters, err := auth.repo.Cluster().ListClustersByProjectID(uint(projID))
-
-		if err != nil {
-			http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
-			return
-		}
-
-		doesExist := false
-
-		for _, cluster := range clusters {
-			if cluster.ID == uint(clusterID) {
-				doesExist = true
-				break
-			}
-		}
-
-		if doesExist {
-			next.ServeHTTP(w, r)
-			return
-		}
-
-		http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
-		return
-	})
-}
-
-// DoesUserHaveInviteAccess looks for a project_id parameter and a
-// invite_id parameter, and verifies that the invite belongs
-// to the project
-func (auth *Auth) DoesUserHaveInviteAccess(
-	next http.Handler,
-	projLoc IDLocation,
-	inviteLoc IDLocation,
-) http.Handler {
-	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		inviteID, err := findInviteIDInRequest(r, inviteLoc)
-
-		if err != nil {
-			http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
-			return
-		}
-
-		projID, err := findProjIDInRequest(r, projLoc)
-
-		if err != nil {
-			http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
-			return
-		}
-
-		// get the service accounts belonging to the project
-		invites, err := auth.repo.Invite().ListInvitesByProjectID(uint(projID))
-
-		if err != nil {
-			http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
-			return
-		}
-
-		doesExist := false
-
-		for _, invite := range invites {
-			if invite.ID == uint(inviteID) {
-				doesExist = true
-				break
-			}
-		}
-
-		if doesExist {
-			next.ServeHTTP(w, r)
-			return
-		}
-
-		http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
-		return
-	})
-}
-
-// DoesUserHaveRegistryAccess looks for a project_id parameter and a
-// registry_id parameter, and verifies that the registry belongs
-// to the project
-func (auth *Auth) DoesUserHaveRegistryAccess(
-	next http.Handler,
-	projLoc IDLocation,
-	registryLoc IDLocation,
-) http.Handler {
-	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		regID, err := findRegistryIDInRequest(r, registryLoc)
-
-		if err != nil {
-			http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
-			return
-		}
-
-		projID, err := findProjIDInRequest(r, projLoc)
-
-		if err != nil {
-			http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
-			return
-		}
-
-		// get the service accounts belonging to the project
-		regs, err := auth.repo.Registry().ListRegistriesByProjectID(uint(projID))
-
-		if err != nil {
-			http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
-			return
-		}
-
-		doesExist := false
-
-		for _, reg := range regs {
-			if reg.ID == uint(regID) {
-				doesExist = true
-				break
-			}
-		}
-
-		if doesExist {
-			next.ServeHTTP(w, r)
-			return
-		}
-
-		http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
-		return
-	})
-}
-
-// DoesUserHaveGitInstallationAccess checks that a user has access to an installation id
-// by ensuring the installation id exists for one org or account they have access to
-// note that this makes a github API request, but the endpoint is fast so this doesn't add
-// much overhead
-func (auth *Auth) DoesUserHaveGitInstallationAccess(
-	next http.Handler,
-	gitRepoLoc IDLocation,
-) http.Handler {
-	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		grID, err := findGitInstallationIDInRequest(r, gitRepoLoc)
-
-		if err != nil {
-			http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
-			return
-		}
-
-		tok := auth.getTokenFromRequest(r)
-
-		var userID uint
-
-		if tok != nil {
-			userID = tok.IBy
-		} else {
-			session, err := auth.store.Get(r, auth.cookieName)
-
-			if err != nil {
-				http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
-				return
-			}
-
-			sessionUserID, ok := session.Values["user_id"]
-
-			if !ok {
-				http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
-				return
-			}
-
-			userID, ok = sessionUserID.(uint)
-
-			if !ok {
-				http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
-				return
-			}
-		}
-
-		user, err := auth.repo.User().ReadUser(userID)
-
-		if err != nil {
-			http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
-			return
-		}
-
-		oauthInt, err := auth.repo.GithubAppOAuthIntegration().ReadGithubAppOauthIntegration(user.GithubAppIntegrationID)
-
-		if err != nil {
-			http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
-			return
-		}
-
-		_, _, err = oauth.GetAccessToken(oauthInt.SharedOAuthModel,
-			auth.GithubAppConf,
-			oauth.MakeUpdateGithubAppOauthIntegrationFunction(oauthInt, auth.repo))
-
-		if err != nil {
-			http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
-			return
-		}
-
-		client := github.NewClient(auth.GithubAppConf.Client(oauth2.NoContext, &oauth2.Token{
-			AccessToken:  string(oauthInt.AccessToken),
-			RefreshToken: string(oauthInt.RefreshToken),
-			TokenType:    "Bearer",
-		}))
-
-		accountIDs := make([]int64, 0)
-
-		AuthUser, _, err := client.Users.Get(context.Background(), "")
-
-		if err != nil {
-			http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
-			return
-		}
-
-		accountIDs = append(accountIDs, *AuthUser.ID)
-
-		opts := &github.ListOptions{
-			PerPage: 100,
-			Page:    1,
-		}
-
-		for {
-			orgs, pages, err := client.Organizations.List(context.Background(), "", opts)
-
-			if err != nil {
-				http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
-				return
-			}
-
-			for _, org := range orgs {
-				accountIDs = append(accountIDs, *org.ID)
-			}
-
-			if pages.NextPage == 0 {
-				break
-			}
-		}
-
-		installations, err := auth.repo.GithubAppInstallation().ReadGithubAppInstallationByAccountIDs(accountIDs)
-
-		for _, installation := range installations {
-			if uint64(installation.InstallationID) == grID {
-				next.ServeHTTP(w, r)
-				return
-			}
-		}
-
-		http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
-	})
-}
-
-// DoesUserHaveInfraAccess looks for a project_id parameter and an
-// infra_id parameter, and verifies that the infra belongs
-// to the project
-func (auth *Auth) DoesUserHaveInfraAccess(
-	next http.Handler,
-	projLoc IDLocation,
-	infraLoc IDLocation,
-) http.Handler {
-	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		infraID, err := findInfraIDInRequest(r, infraLoc)
-
-		if err != nil {
-			http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
-			return
-		}
-
-		projID, err := findProjIDInRequest(r, projLoc)
-
-		if err != nil {
-			http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
-			return
-		}
-
-		infras, err := auth.repo.Infra().ListInfrasByProjectID(uint(projID))
-
-		if err != nil {
-			http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
-			return
-		}
-
-		doesExist := false
-
-		for _, infra := range infras {
-			if infra.ID == uint(infraID) {
-				doesExist = true
-				break
-			}
-		}
-
-		if doesExist {
-			next.ServeHTTP(w, r)
-			return
-		}
-
-		http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
-		return
-	})
-}
-
-// DoesUserHaveAWSIntegrationAccess looks for a project_id parameter and an
-// aws_integration_id parameter, and verifies that the infra belongs
-// to the project
-func (auth *Auth) DoesUserHaveAWSIntegrationAccess(
-	next http.Handler,
-	projLoc IDLocation,
-	awsLoc IDLocation,
-	optional bool,
-) http.Handler {
-	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		awsID, err := findAWSIntegrationIDInRequest(r, awsLoc)
-
-		if awsID == 0 && optional {
-			next.ServeHTTP(w, r)
-			return
-		}
-
-		if awsID == 0 || err != nil {
-			http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
-			return
-		}
-
-		projID, err := findProjIDInRequest(r, projLoc)
-
-		if err != nil {
-			http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
-			return
-		}
-
-		awsInts, err := auth.repo.AWSIntegration().ListAWSIntegrationsByProjectID(uint(projID))
-
-		if err != nil {
-			http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
-			return
-		}
-
-		doesExist := false
-
-		for _, awsInt := range awsInts {
-			if awsInt.ID == uint(awsID) {
-				doesExist = true
-				break
-			}
-		}
-
-		if doesExist {
-			next.ServeHTTP(w, r)
-			return
-		}
-
-		http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
-		return
-	})
-}
-
-// DoesUserHaveGCPIntegrationAccess looks for a project_id parameter and an
-// gcp_integration_id parameter, and verifies that the infra belongs
-// to the project
-func (auth *Auth) DoesUserHaveGCPIntegrationAccess(
-	next http.Handler,
-	projLoc IDLocation,
-	gcpLoc IDLocation,
-	optional bool,
-) http.Handler {
-	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		gcpID, err := findGCPIntegrationIDInRequest(r, gcpLoc)
-
-		if gcpID == 0 && optional {
-			next.ServeHTTP(w, r)
-			return
-		}
-
-		if gcpID == 0 || err != nil {
-			http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
-			return
-		}
-
-		projID, err := findProjIDInRequest(r, projLoc)
-
-		if err != nil {
-			http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
-			return
-		}
-
-		gcpInts, err := auth.repo.GCPIntegration().ListGCPIntegrationsByProjectID(uint(projID))
-
-		if err != nil {
-			http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
-			return
-		}
-
-		doesExist := false
-
-		for _, awsInt := range gcpInts {
-			if awsInt.ID == uint(gcpID) {
-				doesExist = true
-				break
-			}
-		}
-
-		if doesExist {
-			next.ServeHTTP(w, r)
-			return
-		}
-
-		http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
-		return
-	})
-}
-
-// DoesUserHaveDOIntegrationAccess looks for a project_id parameter and an
-// do_integration_id parameter, and verifies that the infra belongs
-// to the project
-func (auth *Auth) DoesUserHaveDOIntegrationAccess(
-	next http.Handler,
-	projLoc IDLocation,
-	doLoc IDLocation,
-	optional bool,
-) http.Handler {
-	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		doID, err := findDOIntegrationIDInRequest(r, doLoc)
-
-		if doID == 0 && optional {
-			next.ServeHTTP(w, r)
-			return
-		}
-
-		if doID == 0 || err != nil {
-			http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
-			return
-		}
-
-		projID, err := findProjIDInRequest(r, projLoc)
-
-		if err != nil {
-			http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
-			return
-		}
-
-		oauthInts, err := auth.repo.OAuthIntegration().ListOAuthIntegrationsByProjectID(uint(projID))
-
-		if err != nil {
-			http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
-			return
-		}
-
-		doesExist := false
-
-		for _, oauthInt := range oauthInts {
-			if oauthInt.ID == uint(doID) {
-				doesExist = true
-				break
-			}
-		}
-
-		if doesExist {
-			next.ServeHTTP(w, r)
-			return
-		}
-
-		http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
-		return
-	})
-}
-
-// Helpers
-func (auth *Auth) doesSessionMatchID(r *http.Request, id uint) bool {
-	session, _ := auth.store.Get(r, auth.cookieName)
-
-	userID, ok := session.Values["user_id"]
-
-	if !ok {
-		return false
-	}
-
-	if sessID, ok := userID.(uint); !ok || sessID != id {
-		return false
-	}
-
-	return true
-}
-
-func (auth *Auth) isLoggedIn(w http.ResponseWriter, r *http.Request) bool {
-	// first check for Bearer token
-
-	tok := auth.getTokenFromRequest(r)
-
-	if tok != nil {
-		return true
-	}
-
-	session, err := auth.store.Get(r, auth.cookieName)
-	if err != nil {
-		session.Values["authenticated"] = false
-		if err := session.Save(r, w); err != nil {
-			return false
-		}
-		return false
-	}
-
-	if auth, ok := session.Values["authenticated"].(bool); !auth || !ok {
-		return false
-	}
-	return true
-}
-
-func (auth *Auth) getTokenFromRequest(r *http.Request) *token.Token {
-	reqToken := r.Header.Get("Authorization")
-
-	splitToken := strings.Split(reqToken, "Bearer")
-
-	if len(splitToken) != 2 {
-		return nil
-	}
-
-	reqToken = strings.TrimSpace(splitToken[1])
-
-	tok, err := token.GetTokenFromEncoded(reqToken, auth.tokenConf)
-
-	if err != nil {
-		return nil
-	}
-
-	return tok
-}
-
-func findUserIDInRequest(r *http.Request, userLoc IDLocation) (uint64, error) {
-	var userID uint64
-	var err error
-
-	if userLoc == URLParam {
-		userID, err = strconv.ParseUint(chi.URLParam(r, "user_id"), 0, 64)
-
-		if err != nil {
-			return 0, err
-		}
-	} else if userLoc == BodyParam {
-		form := &bodyUserID{}
-		body, err := ioutil.ReadAll(r.Body)
-
-		if err != nil {
-			return 0, err
-		}
-
-		err = json.Unmarshal(body, form)
-
-		if err != nil {
-			return 0, err
-		}
-
-		userID = form.UserID
-
-		// need to create a new stream for the body
-		r.Body = ioutil.NopCloser(bytes.NewReader(body))
-	} else {
-		vals, err := url.ParseQuery(r.URL.RawQuery)
-
-		if err != nil {
-			return 0, err
-		}
-
-		if userStrArr, ok := vals["user_id"]; ok && len(userStrArr) == 1 {
-			userID, err = strconv.ParseUint(userStrArr[0], 10, 64)
-		} else {
-			return 0, errors.New("user id not found")
-		}
-	}
-
-	return userID, nil
-}
-
-func findProjIDInRequest(r *http.Request, projLoc IDLocation) (uint64, error) {
-	var projID uint64
-	var err error
-
-	if projLoc == URLParam {
-		projID, err = strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
-
-		if err != nil {
-			return 0, err
-		}
-	} else if projLoc == BodyParam {
-		form := &bodyProjectID{}
-		body, err := ioutil.ReadAll(r.Body)
-
-		if err != nil {
-			return 0, err
-		}
-
-		err = json.Unmarshal(body, form)
-
-		if err != nil {
-			return 0, err
-		}
-
-		projID = form.ProjectID
-
-		// need to create a new stream for the body
-		r.Body = ioutil.NopCloser(bytes.NewReader(body))
-	} else {
-		vals, err := url.ParseQuery(r.URL.RawQuery)
-
-		if err != nil {
-			return 0, err
-		}
-
-		if projStrArr, ok := vals["project_id"]; ok && len(projStrArr) == 1 {
-			projID, err = strconv.ParseUint(projStrArr[0], 10, 64)
-		} else {
-			return 0, errors.New("project id not found")
-		}
-	}
-
-	return projID, nil
-}
-
-func findClusterIDInRequest(r *http.Request, clusterLoc IDLocation) (uint64, error) {
-	var clusterID uint64
-	var err error
-
-	if clusterLoc == URLParam {
-		clusterID, err = strconv.ParseUint(chi.URLParam(r, "cluster_id"), 0, 64)
-
-		if err != nil {
-			return 0, err
-		}
-	} else if clusterLoc == BodyParam {
-		form := &bodyClusterID{}
-		body, err := ioutil.ReadAll(r.Body)
-
-		if err != nil {
-			return 0, err
-		}
-
-		err = json.Unmarshal(body, form)
-
-		if err != nil {
-			return 0, err
-		}
-
-		clusterID = form.ClusterID
-
-		// need to create a new stream for the body
-		r.Body = ioutil.NopCloser(bytes.NewReader(body))
-	} else {
-		vals, err := url.ParseQuery(r.URL.RawQuery)
-
-		if err != nil {
-			return 0, err
-		}
-
-		if clStrArr, ok := vals["cluster_id"]; ok && len(clStrArr) == 1 {
-			clusterID, err = strconv.ParseUint(clStrArr[0], 10, 64)
-		} else {
-			return 0, errors.New("cluster id not found")
-		}
-	}
-
-	return clusterID, nil
-}
-
-func findInviteIDInRequest(r *http.Request, inviteLoc IDLocation) (uint64, error) {
-	var inviteID uint64
-	var err error
-
-	if inviteLoc == URLParam {
-		inviteID, err = strconv.ParseUint(chi.URLParam(r, "invite_id"), 0, 64)
-
-		if err != nil {
-			return 0, err
-		}
-	} else if inviteLoc == BodyParam {
-		form := &bodyInviteID{}
-		body, err := ioutil.ReadAll(r.Body)
-
-		if err != nil {
-			return 0, err
-		}
-
-		err = json.Unmarshal(body, form)
-
-		if err != nil {
-			return 0, err
-		}
-
-		inviteID = form.InviteID
-
-		// need to create a new stream for the body
-		r.Body = ioutil.NopCloser(bytes.NewReader(body))
-	} else {
-		vals, err := url.ParseQuery(r.URL.RawQuery)
-
-		if err != nil {
-			return 0, err
-		}
-
-		if invStrArr, ok := vals["invite_id"]; ok && len(invStrArr) == 1 {
-			inviteID, err = strconv.ParseUint(invStrArr[0], 10, 64)
-		} else {
-			return 0, errors.New("invite id not found")
-		}
-	}
-
-	return inviteID, nil
-}
-
-func findRegistryIDInRequest(r *http.Request, registryLoc IDLocation) (uint64, error) {
-	var regID uint64
-	var err error
-
-	if registryLoc == URLParam {
-		regID, err = strconv.ParseUint(chi.URLParam(r, "registry_id"), 0, 64)
-
-		if err != nil {
-			return 0, err
-		}
-	} else if registryLoc == BodyParam {
-		form := &bodyRegistryID{}
-		body, err := ioutil.ReadAll(r.Body)
-
-		if err != nil {
-			return 0, err
-		}
-
-		err = json.Unmarshal(body, form)
-
-		if err != nil {
-			return 0, err
-		}
-
-		regID = form.RegistryID
-
-		// need to create a new stream for the body
-		r.Body = ioutil.NopCloser(bytes.NewReader(body))
-	} else {
-		vals, err := url.ParseQuery(r.URL.RawQuery)
-
-		if err != nil {
-			return 0, err
-		}
-
-		if regStrArr, ok := vals["registry_id"]; ok && len(regStrArr) == 1 {
-			regID, err = strconv.ParseUint(regStrArr[0], 10, 64)
-		} else {
-			return 0, errors.New("registry id not found")
-		}
-	}
-
-	return regID, nil
-}
-
-// findGitInstallationIDInRequest extracts and installation ID from a request
-func findGitInstallationIDInRequest(r *http.Request, gitRepoLoc IDLocation) (uint64, error) {
-	var grID uint64
-	var err error
-
-	if gitRepoLoc == URLParam {
-		grID, err = strconv.ParseUint(chi.URLParam(r, "installation_id"), 0, 64)
-
-		if err != nil {
-			return 0, err
-		}
-	} else if gitRepoLoc == BodyParam {
-		form := &bodyGitRepoID{}
-		body, err := ioutil.ReadAll(r.Body)
-
-		if err != nil {
-			return 0, err
-		}
-
-		err = json.Unmarshal(body, form)
-
-		if err != nil {
-			return 0, err
-		}
-
-		grID = form.GitRepoID
-
-		// need to create a new stream for the body
-		r.Body = ioutil.NopCloser(bytes.NewReader(body))
-	} else {
-		vals, err := url.ParseQuery(r.URL.RawQuery)
-
-		if err != nil {
-			return 0, err
-		}
-
-		if regStrArr, ok := vals["installation_id"]; ok && len(regStrArr) == 1 {
-			grID, err = strconv.ParseUint(regStrArr[0], 10, 64)
-		} else {
-			return 0, errors.New("git app installation id not found")
-		}
-	}
-
-	return grID, nil
-}
-
-func findInfraIDInRequest(r *http.Request, infraLoc IDLocation) (uint64, error) {
-	var infraID uint64
-	var err error
-
-	if infraLoc == URLParam {
-		infraID, err = strconv.ParseUint(chi.URLParam(r, "infra_id"), 0, 64)
-
-		if err != nil {
-			return 0, err
-		}
-	} else if infraLoc == BodyParam {
-		form := &bodyInfraID{}
-		body, err := ioutil.ReadAll(r.Body)
-
-		if err != nil {
-			return 0, err
-		}
-
-		err = json.Unmarshal(body, form)
-
-		if err != nil {
-			return 0, err
-		}
-
-		infraID = form.InfraID
-
-		// need to create a new stream for the body
-		r.Body = ioutil.NopCloser(bytes.NewReader(body))
-	} else {
-		vals, err := url.ParseQuery(r.URL.RawQuery)
-
-		if err != nil {
-			return 0, err
-		}
-
-		if regStrArr, ok := vals["infra_id"]; ok && len(regStrArr) == 1 {
-			infraID, err = strconv.ParseUint(regStrArr[0], 10, 64)
-		} else {
-			return 0, errors.New("infra id not found")
-		}
-	}
-
-	return infraID, nil
-}
-
-func findAWSIntegrationIDInRequest(r *http.Request, awsLoc IDLocation) (uint64, error) {
-	var awsID uint64
-	var err error
-
-	if awsLoc == URLParam {
-		awsID, err = strconv.ParseUint(chi.URLParam(r, "aws_integration_id"), 0, 64)
-
-		if err != nil {
-			return 0, err
-		}
-	} else if awsLoc == BodyParam {
-		form := &bodyAWSIntegrationID{}
-		body, err := ioutil.ReadAll(r.Body)
-
-		if err != nil {
-			return 0, err
-		}
-
-		err = json.Unmarshal(body, form)
-
-		if err != nil {
-			return 0, err
-		}
-
-		awsID = form.AWSIntegrationID
-
-		// need to create a new stream for the body
-		r.Body = ioutil.NopCloser(bytes.NewReader(body))
-	} else {
-		vals, err := url.ParseQuery(r.URL.RawQuery)
-
-		if err != nil {
-			return 0, err
-		}
-
-		if regStrArr, ok := vals["aws_integration_id"]; ok && len(regStrArr) == 1 {
-			awsID, err = strconv.ParseUint(regStrArr[0], 10, 64)
-		} else {
-			return 0, errors.New("aws integration id not found")
-		}
-	}
-
-	return awsID, nil
-}
-
-func findGCPIntegrationIDInRequest(r *http.Request, gcpLoc IDLocation) (uint64, error) {
-	var gcpID uint64
-	var err error
-
-	if gcpLoc == URLParam {
-		gcpID, err = strconv.ParseUint(chi.URLParam(r, "gcp_integration_id"), 0, 64)
-
-		if err != nil {
-			return 0, err
-		}
-	} else if gcpLoc == BodyParam {
-		form := &bodyGCPIntegrationID{}
-		body, err := ioutil.ReadAll(r.Body)
-
-		if err != nil {
-			return 0, err
-		}
-
-		err = json.Unmarshal(body, form)
-
-		if err != nil {
-			return 0, err
-		}
-
-		gcpID = form.GCPIntegrationID
-
-		// need to create a new stream for the body
-		r.Body = ioutil.NopCloser(bytes.NewReader(body))
-	} else {
-		vals, err := url.ParseQuery(r.URL.RawQuery)
-
-		if err != nil {
-			return 0, err
-		}
-
-		if regStrArr, ok := vals["gcp_integration_id"]; ok && len(regStrArr) == 1 {
-			gcpID, err = strconv.ParseUint(regStrArr[0], 10, 64)
-		} else {
-			return 0, errors.New("gcp integration id not found")
-		}
-	}
-
-	return gcpID, nil
-}
-
-func findDOIntegrationIDInRequest(r *http.Request, doLoc IDLocation) (uint64, error) {
-	var doID uint64
-	var err error
-
-	if doLoc == URLParam {
-		doID, err = strconv.ParseUint(chi.URLParam(r, "do_integration_id"), 0, 64)
-
-		if err != nil {
-			return 0, err
-		}
-	} else if doLoc == BodyParam {
-		form := &bodyDOIntegrationID{}
-		body, err := ioutil.ReadAll(r.Body)
-
-		if err != nil {
-			return 0, err
-		}
-
-		err = json.Unmarshal(body, form)
-
-		if err != nil {
-			return 0, err
-		}
-
-		doID = form.DOIntegrationID
-
-		// need to create a new stream for the body
-		r.Body = ioutil.NopCloser(bytes.NewReader(body))
-	} else {
-		vals, err := url.ParseQuery(r.URL.RawQuery)
-
-		if err != nil {
-			return 0, err
-		}
-
-		if regStrArr, ok := vals["do_integration_id"]; ok && len(regStrArr) == 1 {
-			doID, err = strconv.ParseUint(regStrArr[0], 10, 64)
-		} else {
-			return 0, errors.New("do integration id not found")
-		}
-	}
-
-	return doID, nil
-}

+ 0 - 11
server/middleware/json.go

@@ -1,11 +0,0 @@
-package middleware
-
-import "net/http"
-
-// ContentTypeJSON sets the content type for requests to application/json
-func ContentTypeJSON(next http.Handler) http.Handler {
-	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		w.Header().Set("Content-Type", "application/json;charset=utf8")
-		next.ServeHTTP(w, r)
-	})
-}

+ 0 - 76
server/middleware/requestlog/handler.go

@@ -1,76 +0,0 @@
-package requestlog
-
-import (
-	"io"
-	"io/ioutil"
-	"net/http"
-	"time"
-
-	lr "github.com/porter-dev/porter/internal/logger"
-)
-
-// Handler wraps an HTTP handler so that a log entry can be generated for
-// request/response stats
-type Handler struct {
-	handler http.Handler
-	logger  *lr.Logger
-}
-
-// NewHandler creates a new Handler instance based on an http.HandlerFunc
-func NewHandler(h http.HandlerFunc, l *lr.Logger) *Handler {
-	return &Handler{
-		handler: h,
-		logger:  l,
-	}
-}
-
-// ServeHTTP calls its underlying handler's ServeHTTP method, then calls
-// Log after the handler returns.
-//
-// ServeHTTP will always consume the request body up to the first error,
-// even if the underlying handler does not.
-func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
-	start := time.Now()
-	le := &logEntry{
-		ReceivedTime:      start,
-		RequestMethod:     r.Method,
-		RequestURL:        r.URL.String(),
-		RequestHeaderSize: headerSize(r.Header),
-		UserAgent:         r.UserAgent(),
-		Referer:           r.Referer(),
-		Proto:             r.Proto,
-	}
-
-	r2 := new(http.Request)
-	*r2 = *r
-	rcc := &readCounterCloser{r: r.Body}
-	r2.Body = rcc
-	w2 := &responseStats{w: w}
-	h.handler.ServeHTTP(w2, r2)
-	le.Latency = time.Since(start)
-	if rcc.err == nil && rcc.r != nil {
-		// If the handler hasn't encountered an error in the Body (like EOF),
-		// then consume the rest of the Body to provide an accurate rcc.n.
-		io.Copy(ioutil.Discard, rcc)
-	}
-	le.RequestBodySize = rcc.n
-	le.Status = w2.code
-	if le.Status == 0 {
-		le.Status = http.StatusOK
-	}
-	le.ResponseHeaderSize, le.ResponseBodySize = w2.size()
-	h.logger.Info().
-		Time("received_time", le.ReceivedTime).
-		Str("method", le.RequestMethod).
-		Str("url", le.RequestURL).
-		Int64("header_size", le.RequestHeaderSize).
-		Int64("body_size", le.RequestBodySize).
-		Str("agent", le.UserAgent).
-		Str("referer", le.Referer).
-		Str("proto", le.Proto).
-		Int("status", le.Status).
-		Int64("resp_header_size", le.ResponseHeaderSize).
-		Int64("resp_body_size", le.ResponseBodySize).
-		Dur("latency", le.Latency).
-		Msg("")
-}

+ 0 - 110
server/middleware/requestlog/log_entry.go

@@ -1,110 +0,0 @@
-package requestlog
-
-import (
-	"bufio"
-	"errors"
-	"io"
-	"net"
-	"net/http"
-	"time"
-)
-
-type logEntry struct {
-	ReceivedTime       time.Time
-	RequestMethod      string
-	RequestURL         string
-	RequestHeaderSize  int64
-	RequestBodySize    int64
-	UserAgent          string
-	Referer            string
-	Proto              string
-	Status             int
-	ResponseHeaderSize int64
-	ResponseBodySize   int64
-	Latency            time.Duration
-}
-
-func ipFromHostPort(hp string) string {
-	h, _, err := net.SplitHostPort(hp)
-	if err != nil {
-		return ""
-	}
-	if len(h) > 0 && h[0] == '[' {
-		return h[1 : len(h)-1]
-	}
-	return h
-}
-
-type readCounterCloser struct {
-	r   io.ReadCloser
-	n   int64
-	err error
-}
-
-func (rcc *readCounterCloser) Read(p []byte) (n int, err error) {
-	if rcc.err != nil {
-		return 0, rcc.err
-	}
-	n, rcc.err = rcc.r.Read(p)
-	rcc.n += int64(n)
-	return n, rcc.err
-}
-func (rcc *readCounterCloser) Close() error {
-	rcc.err = errors.New("read from closed reader")
-	return rcc.r.Close()
-}
-
-type writeCounter int64
-
-func (wc *writeCounter) Write(p []byte) (n int, err error) {
-	*wc += writeCounter(len(p))
-	return len(p), nil
-}
-func headerSize(h http.Header) int64 {
-	var wc writeCounter
-	h.Write(&wc)
-	return int64(wc) + 2 // for CRLF
-}
-
-type responseStats struct {
-	w     http.ResponseWriter
-	hsize int64
-	wc    writeCounter
-	code  int
-}
-
-func (r *responseStats) Header() http.Header {
-	return r.w.Header()
-}
-func (r *responseStats) WriteHeader(statusCode int) {
-	if r.code != 0 {
-		return
-	}
-	r.hsize = headerSize(r.w.Header())
-	r.w.WriteHeader(statusCode)
-	r.code = statusCode
-}
-func (r *responseStats) Write(p []byte) (n int, err error) {
-	if r.code == 0 {
-		r.WriteHeader(http.StatusOK)
-	}
-	n, err = r.w.Write(p)
-	r.wc.Write(p[:n])
-	return
-}
-func (r *responseStats) Hijack() (net.Conn, *bufio.ReadWriter, error) {
-	h, ok := r.w.(http.Hijacker)
-	if !ok {
-		return nil, nil, errors.New("ResponseWriter Interface does not support hijacking")
-	}
-	return h.Hijack()
-}
-func (r *responseStats) size() (hdr, body int64) {
-	if r.code == 0 {
-		return headerSize(r.w.Header()), 0
-	}
-	// Use the header size from the time WriteHeader was called.
-	// The Header map can be mutated after the call to add HTTP Trailers,
-	// which we don't want to count.
-	return r.hsize, int64(r.wc)
-}

+ 0 - 1862
server/router/router.go

@@ -1,1862 +0,0 @@
-package router
-
-import (
-	"net/http"
-	"os"
-	"path"
-	"strings"
-	"time"
-
-	"github.com/go-chi/chi"
-	"github.com/go-chi/chi/middleware"
-	"github.com/porter-dev/porter/internal/auth/token"
-	"github.com/porter-dev/porter/server/api"
-	mw "github.com/porter-dev/porter/server/middleware"
-	"github.com/porter-dev/porter/server/middleware/requestlog"
-	"golang.org/x/oauth2"
-)
-
-// New creates a new Chi router instance and registers all routes supported by the
-// API
-func New(a *api.App) *chi.Mux {
-	l := a.Logger
-	r := chi.NewRouter()
-
-	var ghAppConf *oauth2.Config
-
-	if a.GithubAppConf != nil {
-		ghAppConf = &a.GithubAppConf.Config
-	}
-
-	auth := mw.NewAuth(a.Store, a.ServerConf.CookieName, &token.TokenGeneratorConf{
-		TokenSecret: a.ServerConf.TokenGeneratorSecret,
-	}, a.Repo, ghAppConf)
-
-	r.Route("/api", func(r chi.Router) {
-		r.Use(mw.ContentTypeJSON)
-
-		// Group for default operations with 10s timeout
-		r.Group(func(r chi.Router) {
-			r.Use(middleware.Timeout(10 * time.Second))
-
-			// health checks
-			r.Method("GET", "/livez", http.HandlerFunc(a.HandleLive))
-			r.Method("GET", "/readyz", http.HandlerFunc(a.HandleReady))
-
-			// /api/users routes
-			r.Method(
-				"GET",
-				"/users/{user_id}",
-				auth.DoesUserIDMatch(
-					requestlog.NewHandler(a.HandleReadUser, l),
-					mw.URLParam,
-				),
-			)
-
-			r.Method(
-				"GET",
-				"/users/{user_id}/projects",
-				auth.DoesUserIDMatch(
-					requestlog.NewHandler(a.HandleListUserProjects, l),
-					mw.URLParam,
-				),
-			)
-
-			// only allow basic create user or basic login if BasicLogin feature is set
-			if a.Capabilities.BasicLogin {
-				r.Method(
-					"POST",
-					"/users",
-					requestlog.NewHandler(a.HandleCreateUser, l),
-				)
-
-				r.Method(
-					"POST",
-					"/login",
-					requestlog.NewHandler(a.HandleLoginUser, l),
-				)
-			}
-
-			r.Method(
-				"DELETE",
-				"/users/{user_id}",
-				auth.DoesUserIDMatch(
-					requestlog.NewHandler(a.HandleDeleteUser, l),
-					mw.URLParam,
-				),
-			)
-
-			r.Method(
-				"GET",
-				"/cli/login",
-				auth.BasicAuthenticateWithRedirect(
-					requestlog.NewHandler(a.HandleCLILoginUser, l),
-				),
-			)
-
-			r.Method(
-				"GET",
-				"/cli/login/exchange",
-				requestlog.NewHandler(a.HandleCLILoginExchangeToken, l),
-			)
-
-			r.Method(
-				"GET",
-				"/auth/check",
-				auth.BasicAuthenticate(
-					requestlog.NewHandler(a.HandleAuthCheck, l),
-				),
-			)
-
-			r.Method(
-				"POST",
-				"/logout",
-				auth.BasicAuthenticate(
-					requestlog.NewHandler(a.HandleLogoutUser, l),
-				),
-			)
-
-			r.Method(
-				"POST",
-				"/email/verify/initiate",
-				auth.BasicAuthenticate(
-					requestlog.NewHandler(a.InitiateEmailVerifyUser, l),
-				),
-			)
-
-			r.Method(
-				"GET",
-				"/email/verify/finalize",
-				auth.BasicAuthenticateWithRedirect(
-					requestlog.NewHandler(a.FinalizeEmailVerifyUser, l),
-				),
-			)
-
-			r.Method(
-				"POST",
-				"/password/reset/initiate",
-				requestlog.NewHandler(a.InitiatePWResetUser, l),
-			)
-
-			r.Method(
-				"POST",
-				"/password/reset/verify",
-				requestlog.NewHandler(a.VerifyPWResetUser, l),
-			)
-
-			r.Method(
-				"POST",
-				"/password/reset/finalize",
-				requestlog.NewHandler(a.FinalizPWResetUser, l),
-			)
-
-			// /api/integrations routes
-			r.Method(
-				"GET",
-				"/integrations/cluster",
-				auth.BasicAuthenticate(
-					requestlog.NewHandler(a.HandleListClusterIntegrations, l),
-				),
-			)
-
-			r.Method(
-				"GET",
-				"/integrations/registry",
-				auth.BasicAuthenticate(
-					requestlog.NewHandler(a.HandleListRegistryIntegrations, l),
-				),
-			)
-
-			r.Method(
-				"GET",
-				"/integrations/helm",
-				auth.BasicAuthenticate(
-					requestlog.NewHandler(a.HandleListHelmRepoIntegrations, l),
-				),
-			)
-
-			r.Method(
-				"GET",
-				"/integrations/repo",
-				auth.BasicAuthenticate(
-					requestlog.NewHandler(a.HandleListRepoIntegrations, l),
-				),
-			)
-
-			r.Method(
-				"POST",
-				"/integrations/github-app/webhook",
-				requestlog.NewHandler(a.HandleGithubAppEvent, l),
-			)
-
-			r.Method(
-				"GET",
-				"/integrations/github-app/authorize",
-				requestlog.NewHandler(a.HandleGithubAppAuthorize, l),
-			)
-
-			r.Method(
-				"GET",
-				"/integrations/github-app/oauth",
-				requestlog.NewHandler(a.HandleGithubAppOauthInit, l),
-			)
-
-			r.Method(
-				"GET",
-				"/integrations/github-app/install",
-				requestlog.NewHandler(a.HandleGithubAppInstall, l),
-			)
-
-			r.Method(
-				"GET",
-				"/integrations/github-app/access",
-				auth.BasicAuthenticate(
-					requestlog.NewHandler(a.HandleListGithubAppAccess, l),
-				),
-			)
-
-			// /api/templates routes
-			r.Method(
-				"GET",
-				"/templates",
-				auth.BasicAuthenticate(
-					requestlog.NewHandler(a.HandleListTemplates, l),
-				),
-			)
-
-			r.Method(
-				"GET",
-				"/templates/{name}/{version}",
-				auth.BasicAuthenticate(
-					requestlog.NewHandler(a.HandleReadTemplate, l),
-				),
-			)
-
-			r.Method(
-				"GET",
-				"/templates/upgrade_notes/{name}/{version}",
-				auth.BasicAuthenticate(
-					requestlog.NewHandler(a.HandleGetTemplateUpgradeNotes, l),
-				),
-			)
-
-			// /api/oauth routes
-			r.Method(
-				"GET",
-				"/oauth/projects/{project_id}/github",
-				auth.DoesUserHaveProjectAccess(
-					requestlog.NewHandler(a.HandleGithubOAuthStartProject, l),
-					mw.URLParam,
-					mw.WriteAccess,
-				),
-			)
-			r.Method(
-				"GET",
-				"/oauth/login/github",
-				requestlog.NewHandler(a.HandleGithubOAuthStartUser, l),
-			)
-
-			r.Method(
-				"GET",
-				"/oauth/github/callback",
-				requestlog.NewHandler(a.HandleGithubOAuthCallback, l),
-			)
-
-			r.Method(
-				"GET",
-				"/oauth/github-app/callback",
-				requestlog.NewHandler(a.HandleGithubAppOAuthCallback, l),
-			)
-
-			r.Method(
-				"GET",
-				"/oauth/login/google",
-				requestlog.NewHandler(a.HandleGoogleStartUser, l),
-			)
-
-			r.Method(
-				"GET",
-				"/oauth/google/callback",
-				requestlog.NewHandler(a.HandleGoogleOAuthCallback, l),
-			)
-
-			r.Method(
-				"GET",
-				"/oauth/projects/{project_id}/digitalocean",
-				auth.DoesUserHaveProjectAccess(
-					requestlog.NewHandler(a.HandleDOOAuthStartProject, l),
-					mw.URLParam,
-					mw.WriteAccess,
-				),
-			)
-
-			r.Method(
-				"GET",
-				"/oauth/digitalocean/callback",
-				requestlog.NewHandler(a.HandleDOOAuthCallback, l),
-			)
-
-			r.Method(
-				"GET",
-				"/oauth/projects/{project_id}/slack",
-				auth.DoesUserHaveProjectAccess(
-					requestlog.NewHandler(a.HandleSlackOAuthStartProject, l),
-					mw.URLParam,
-					mw.WriteAccess,
-				),
-			)
-
-			r.Method(
-				"GET",
-				"/oauth/slack/callback",
-				requestlog.NewHandler(a.HandleSlackOAuthCallback, l),
-			)
-
-			// /api/projects routes
-			r.Method(
-				"GET",
-				"/projects/{project_id}",
-				auth.DoesUserHaveProjectAccess(
-					requestlog.NewHandler(a.HandleReadProject, l),
-					mw.URLParam,
-					mw.ReadAccess,
-				),
-			)
-
-			r.Method(
-				"GET",
-				"/projects/{project_id}/policy",
-				auth.DoesUserHaveProjectAccess(
-					requestlog.NewHandler(a.HandleReadProjectPolicy, l),
-					mw.URLParam,
-					mw.ReadAccess,
-				),
-			)
-
-			r.Method(
-				"GET",
-				"/projects/{project_id}/roles",
-				auth.DoesUserHaveProjectAccess(
-					requestlog.NewHandler(a.HandleGetProjectRoles, l),
-					mw.URLParam,
-					mw.AdminAccess,
-				),
-			)
-
-			r.Method(
-				"GET",
-				"/projects/{project_id}/collaborators",
-				auth.DoesUserHaveProjectAccess(
-					requestlog.NewHandler(a.HandleListProjectCollaborators, l),
-					mw.URLParam,
-					mw.AdminAccess,
-				),
-			)
-
-			r.Method(
-				"POST",
-				"/projects/{project_id}/roles/{user_id}",
-				auth.DoesUserHaveProjectAccess(
-					requestlog.NewHandler(a.HandleUpdateProjectRole, l),
-					mw.URLParam,
-					mw.AdminAccess,
-				),
-			)
-
-			r.Method(
-				"POST",
-				"/projects",
-				auth.BasicAuthenticate(
-					requestlog.NewHandler(a.HandleCreateProject, l),
-				),
-			)
-
-			r.Method(
-				"DELETE",
-				"/projects/{project_id}",
-				auth.DoesUserHaveProjectAccess(
-					requestlog.NewHandler(a.HandleDeleteProject, l),
-					mw.URLParam,
-					mw.AdminAccess,
-				),
-			)
-
-			r.Method(
-				"DELETE",
-				"/projects/{project_id}/roles/{user_id}",
-				auth.DoesUserHaveProjectAccess(
-					requestlog.NewHandler(a.HandleDeleteProjectRole, l),
-					mw.URLParam,
-					mw.AdminAccess,
-				),
-			)
-
-			// /api/projects/{project_id}/ci routes
-			r.Method(
-				"POST",
-				"/projects/{project_id}/ci/actions/generate",
-				auth.DoesUserHaveProjectAccess(
-					auth.DoesUserHaveClusterAccess(
-						requestlog.NewHandler(a.HandleGenerateGitAction, l),
-						mw.URLParam,
-						mw.QueryParam,
-					),
-					mw.URLParam,
-					mw.WriteAccess,
-				),
-			)
-
-			r.Method(
-				"POST",
-				"/projects/{project_id}/ci/actions/create",
-				auth.DoesUserHaveProjectAccess(
-					auth.DoesUserHaveClusterAccess(
-						requestlog.NewHandler(a.HandleCreateGitAction, l),
-						mw.URLParam,
-						mw.QueryParam,
-					),
-					mw.URLParam,
-					mw.WriteAccess,
-				),
-			)
-
-			// /api/projects/{project_id}/invites routes
-			r.Method(
-				"POST",
-				"/projects/{project_id}/invites",
-				auth.DoesUserHaveProjectAccess(
-					requestlog.NewHandler(a.HandleCreateInvite, l),
-					mw.URLParam,
-					mw.AdminAccess,
-				),
-			)
-
-			r.Method(
-				"GET",
-				"/projects/{project_id}/invites",
-				auth.DoesUserHaveProjectAccess(
-					requestlog.NewHandler(a.HandleListProjectInvites, l),
-					mw.URLParam,
-					mw.AdminAccess,
-				),
-			)
-
-			r.Method(
-				"GET",
-				"/projects/{project_id}/invites/{token}",
-				auth.BasicAuthenticateWithRedirect(
-					requestlog.NewHandler(a.HandleAcceptInvite, l),
-				),
-			)
-
-			r.Method(
-				"POST",
-				"/projects/{project_id}/invites/{invite_id}",
-				auth.DoesUserHaveProjectAccess(
-					auth.DoesUserHaveInviteAccess(
-						requestlog.NewHandler(a.HandleUpdateInviteRole, l),
-						mw.URLParam,
-						mw.URLParam,
-					),
-					mw.URLParam,
-					mw.AdminAccess,
-				),
-			)
-
-			r.Method(
-				"DELETE",
-				"/projects/{project_id}/invites/{invite_id}",
-				auth.DoesUserHaveProjectAccess(
-					auth.DoesUserHaveInviteAccess(
-						requestlog.NewHandler(a.HandleDeleteProjectInvite, l),
-						mw.URLParam,
-						mw.URLParam,
-					),
-					mw.URLParam,
-					mw.AdminAccess,
-				),
-			)
-
-			// /api/projects/{project_id}/infra routes
-			r.Method(
-				"GET",
-				"/projects/{project_id}/infra",
-				auth.DoesUserHaveProjectAccess(
-					requestlog.NewHandler(a.HandleListProjectInfra, l),
-					mw.URLParam,
-					mw.ReadAccess,
-				),
-			)
-
-			// /api/projects/{project_id}/provision routes
-			r.Method(
-				"POST",
-				"/projects/{project_id}/provision/test",
-				auth.DoesUserHaveProjectAccess(
-					requestlog.NewHandler(a.HandleProvisionTestInfra, l),
-					mw.URLParam,
-					mw.WriteAccess,
-				),
-			)
-
-			r.Method(
-				"POST",
-				"/projects/{project_id}/provision/ecr",
-				auth.DoesUserHaveProjectAccess(
-					auth.DoesUserHaveAWSIntegrationAccess(
-						requestlog.NewHandler(a.HandleProvisionAWSECRInfra, l),
-						mw.URLParam,
-						mw.BodyParam,
-						false,
-					),
-					mw.URLParam,
-					mw.WriteAccess,
-				),
-			)
-
-			r.Method(
-				"POST",
-				"/projects/{project_id}/provision/eks",
-				auth.DoesUserHaveProjectAccess(
-					auth.DoesUserHaveAWSIntegrationAccess(
-						requestlog.NewHandler(a.HandleProvisionAWSEKSInfra, l),
-						mw.URLParam,
-						mw.BodyParam,
-						false,
-					),
-					mw.URLParam,
-					mw.WriteAccess,
-				),
-			)
-
-			r.Method(
-				"POST",
-				"/projects/{project_id}/provision/gcr",
-				auth.DoesUserHaveProjectAccess(
-					auth.DoesUserHaveGCPIntegrationAccess(
-						requestlog.NewHandler(a.HandleProvisionGCPGCRInfra, l),
-						mw.URLParam,
-						mw.BodyParam,
-						false,
-					),
-					mw.URLParam,
-					mw.WriteAccess,
-				),
-			)
-
-			r.Method(
-				"POST",
-				"/projects/{project_id}/provision/gke",
-				auth.DoesUserHaveProjectAccess(
-					auth.DoesUserHaveGCPIntegrationAccess(
-						requestlog.NewHandler(a.HandleProvisionGCPGKEInfra, l),
-						mw.URLParam,
-						mw.BodyParam,
-						false,
-					),
-					mw.URLParam,
-					mw.WriteAccess,
-				),
-			)
-
-			r.Method(
-				"POST",
-				"/projects/{project_id}/provision/docr",
-				auth.DoesUserHaveProjectAccess(
-					auth.DoesUserHaveDOIntegrationAccess(
-						requestlog.NewHandler(a.HandleProvisionDODOCRInfra, l),
-						mw.URLParam,
-						mw.BodyParam,
-						false,
-					),
-					mw.URLParam,
-					mw.WriteAccess,
-				),
-			)
-
-			r.Method(
-				"POST",
-				"/projects/{project_id}/provision/doks",
-				auth.DoesUserHaveProjectAccess(
-					auth.DoesUserHaveDOIntegrationAccess(
-						requestlog.NewHandler(a.HandleProvisionDODOKSInfra, l),
-						mw.URLParam,
-						mw.BodyParam,
-						false,
-					),
-					mw.URLParam,
-					mw.WriteAccess,
-				),
-			)
-
-			r.Method(
-				"GET",
-				"/projects/{project_id}/provision/{kind}/{infra_id}/logs",
-				auth.DoesUserHaveProjectAccess(
-					auth.DoesUserHaveInfraAccess(
-						requestlog.NewHandler(a.HandleGetProvisioningLogs, l),
-						mw.URLParam,
-						mw.URLParam,
-					),
-					mw.URLParam,
-					mw.ReadAccess,
-				),
-			)
-
-			r.Method(
-				"POST",
-				"/projects/{project_id}/infra/{infra_id}/ecr/destroy",
-				auth.DoesUserHaveProjectAccess(
-					auth.DoesUserHaveInfraAccess(
-						requestlog.NewHandler(a.HandleDestroyAWSECRInfra, l),
-						mw.URLParam,
-						mw.URLParam,
-					),
-					mw.URLParam,
-					mw.WriteAccess,
-				),
-			)
-
-			r.Method(
-				"POST",
-				"/projects/{project_id}/infra/{infra_id}/test/destroy",
-				auth.DoesUserHaveProjectAccess(
-					auth.DoesUserHaveInfraAccess(
-						requestlog.NewHandler(a.HandleDestroyTestInfra, l),
-						mw.URLParam,
-						mw.URLParam,
-					),
-					mw.URLParam,
-					mw.WriteAccess,
-				),
-			)
-
-			r.Method(
-				"POST",
-				"/projects/{project_id}/infra/{infra_id}/eks/destroy",
-				auth.DoesUserHaveProjectAccess(
-					auth.DoesUserHaveInfraAccess(
-						requestlog.NewHandler(a.HandleDestroyAWSEKSInfra, l),
-						mw.URLParam,
-						mw.URLParam,
-					),
-					mw.URLParam,
-					mw.WriteAccess,
-				),
-			)
-
-			r.Method(
-				"POST",
-				"/projects/{project_id}/infra/{infra_id}/gke/destroy",
-				auth.DoesUserHaveProjectAccess(
-					auth.DoesUserHaveInfraAccess(
-						requestlog.NewHandler(a.HandleDestroyGCPGKEInfra, l),
-						mw.URLParam,
-						mw.URLParam,
-					),
-					mw.URLParam,
-					mw.WriteAccess,
-				),
-			)
-
-			r.Method(
-				"POST",
-				"/projects/{project_id}/infra/{infra_id}/docr/destroy",
-				auth.DoesUserHaveProjectAccess(
-					auth.DoesUserHaveInfraAccess(
-						requestlog.NewHandler(a.HandleDestroyDODOCRInfra, l),
-						mw.URLParam,
-						mw.URLParam,
-					),
-					mw.URLParam,
-					mw.WriteAccess,
-				),
-			)
-
-			r.Method(
-				"POST",
-				"/projects/{project_id}/infra/{infra_id}/doks/destroy",
-				auth.DoesUserHaveProjectAccess(
-					auth.DoesUserHaveInfraAccess(
-						requestlog.NewHandler(a.HandleDestroyDODOKSInfra, l),
-						mw.URLParam,
-						mw.URLParam,
-					),
-					mw.URLParam,
-					mw.WriteAccess,
-				),
-			)
-
-			// /api/projects/{project_id}/clusters routes
-			r.Method(
-				"GET",
-				"/projects/{project_id}/clusters",
-				auth.DoesUserHaveProjectAccess(
-					requestlog.NewHandler(a.HandleListProjectClusters, l),
-					mw.URLParam,
-					mw.ReadAccess,
-				),
-			)
-
-			r.Method(
-				"POST",
-				"/projects/{project_id}/clusters",
-				auth.DoesUserHaveProjectAccess(
-					auth.DoesUserHaveAWSIntegrationAccess(
-						auth.DoesUserHaveGCPIntegrationAccess(
-							requestlog.NewHandler(a.HandleCreateProjectCluster, l),
-							mw.URLParam,
-							mw.BodyParam,
-							true,
-						),
-						mw.URLParam,
-						mw.BodyParam,
-						true,
-					),
-					mw.URLParam,
-					mw.WriteAccess,
-				),
-			)
-
-			r.Method(
-				"GET",
-				"/projects/{project_id}/clusters/{cluster_id}",
-				auth.DoesUserHaveProjectAccess(
-					auth.DoesUserHaveClusterAccess(
-						requestlog.NewHandler(a.HandleReadProjectCluster, l),
-						mw.URLParam,
-						mw.URLParam,
-					),
-					mw.URLParam,
-					mw.ReadAccess,
-				),
-			)
-
-			r.Method(
-				"GET",
-				"/projects/{project_id}/clusters/{cluster_id}/nodes",
-				auth.DoesUserHaveProjectAccess(
-					auth.DoesUserHaveClusterAccess(
-						requestlog.NewHandler(a.HandleListNodes, l),
-						mw.URLParam,
-						mw.URLParam,
-					),
-					mw.URLParam,
-					mw.ReadAccess,
-				),
-			)
-
-			r.Method(
-				"GET",
-				"/projects/{project_id}/clusters/{cluster_id}/node/{node_name}",
-				auth.DoesUserHaveProjectAccess(
-					auth.DoesUserHaveClusterAccess(
-						requestlog.NewHandler(a.HandleGetNode, l),
-						mw.URLParam,
-						mw.URLParam,
-					),
-					mw.URLParam,
-					mw.ReadAccess,
-				),
-			)
-
-			r.Method(
-				"POST",
-				"/projects/{project_id}/clusters/{cluster_id}",
-				auth.DoesUserHaveProjectAccess(
-					auth.DoesUserHaveClusterAccess(
-						requestlog.NewHandler(a.HandleUpdateProjectCluster, l),
-						mw.URLParam,
-						mw.URLParam,
-					),
-					mw.URLParam,
-					mw.WriteAccess,
-				),
-			)
-
-			r.Method(
-				"DELETE",
-				"/projects/{project_id}/clusters/{cluster_id}",
-				auth.DoesUserHaveProjectAccess(
-					auth.DoesUserHaveClusterAccess(
-						requestlog.NewHandler(a.HandleDeleteProjectCluster, l),
-						mw.URLParam,
-						mw.URLParam,
-					),
-					mw.URLParam,
-					mw.WriteAccess,
-				),
-			)
-
-			// /api/projects/{project_id}/clusters/candidates routes
-			r.Method(
-				"POST",
-				"/projects/{project_id}/clusters/candidates",
-				auth.DoesUserHaveProjectAccess(
-					requestlog.NewHandler(a.HandleCreateProjectClusterCandidates, l),
-					mw.URLParam,
-					mw.WriteAccess,
-				),
-			)
-
-			r.Method(
-				"GET",
-				"/projects/{project_id}/clusters/candidates",
-				auth.DoesUserHaveProjectAccess(
-					requestlog.NewHandler(a.HandleListProjectClusterCandidates, l),
-					mw.URLParam,
-					mw.WriteAccess,
-				),
-			)
-
-			r.Method(
-				"POST",
-				"/projects/{project_id}/clusters/candidates/{candidate_id}/resolve",
-				auth.DoesUserHaveProjectAccess(
-					requestlog.NewHandler(a.HandleResolveClusterCandidate, l),
-					mw.URLParam,
-					mw.WriteAccess,
-				),
-			)
-
-			// /api/projects/{project_id}/integrations routes
-			r.Method(
-				"POST",
-				"/projects/{project_id}/integrations/gcp",
-				auth.DoesUserHaveProjectAccess(
-					requestlog.NewHandler(a.HandleCreateGCPIntegration, l),
-					mw.URLParam,
-					mw.WriteAccess,
-				),
-			)
-
-			r.Method(
-				"POST",
-				"/projects/{project_id}/integrations/aws",
-				auth.DoesUserHaveProjectAccess(
-					requestlog.NewHandler(a.HandleCreateAWSIntegration, l),
-					mw.URLParam,
-					mw.WriteAccess,
-				),
-			)
-
-			r.Method(
-				"POST",
-				"/projects/{project_id}/integrations/aws/{aws_integration_id}/overwrite",
-				auth.DoesUserHaveProjectAccess(
-					auth.DoesUserHaveClusterAccess(
-						auth.DoesUserHaveAWSIntegrationAccess(
-							requestlog.NewHandler(a.HandleOverwriteAWSIntegration, l),
-							mw.URLParam,
-							mw.URLParam,
-							false,
-						),
-						mw.URLParam,
-						mw.QueryParam,
-					),
-					mw.URLParam,
-					mw.WriteAccess,
-				),
-			)
-
-			r.Method(
-				"POST",
-				"/projects/{project_id}/integrations/basic",
-				auth.DoesUserHaveProjectAccess(
-					requestlog.NewHandler(a.HandleCreateBasicAuthIntegration, l),
-					mw.URLParam,
-					mw.WriteAccess,
-				),
-			)
-
-			r.Method(
-				"GET",
-				"/projects/{project_id}/integrations/oauth",
-				auth.DoesUserHaveProjectAccess(
-					requestlog.NewHandler(a.HandleListProjectOAuthIntegrations, l),
-					mw.URLParam,
-					mw.WriteAccess,
-				),
-			)
-
-			// /api/projects/{project_id}/slack_integrations routes
-			r.Method(
-				"GET",
-				"/projects/{project_id}/slack_integrations",
-				auth.DoesUserHaveProjectAccess(
-					requestlog.NewHandler(a.HandleListSlackIntegrations, l),
-					mw.URLParam,
-					mw.WriteAccess,
-				),
-			)
-
-			r.Method(
-				"DELETE",
-				"/projects/{project_id}/slack_integrations/{slack_integration_id}",
-				auth.DoesUserHaveProjectAccess(
-					requestlog.NewHandler(a.HandleDeleteSlackIntegration, l),
-					mw.URLParam,
-					mw.WriteAccess,
-				),
-			)
-
-			r.Method(
-				"GET",
-				"/projects/{project_id}/slack_integrations/exists",
-				auth.DoesUserHaveProjectAccess(
-					requestlog.NewHandler(a.HandleSlackIntegrationExists, l),
-					mw.URLParam,
-					mw.WriteAccess,
-				),
-			)
-
-			// /projects/{project_id}/releases/{name}/notifications routes
-			r.Method(
-				"POST",
-				"/projects/{project_id}/releases/{name}/notifications",
-				auth.DoesUserHaveProjectAccess(
-					auth.DoesUserHaveClusterAccess(
-						requestlog.NewHandler(a.HandleUpdateNotificationConfig, l),
-						mw.URLParam,
-						mw.BodyParam,
-					),
-					mw.URLParam,
-					mw.WriteAccess,
-				),
-			)
-
-			r.Method(
-				"GET",
-				"/projects/{project_id}/releases/{name}/notifications",
-				auth.DoesUserHaveProjectAccess(
-					auth.DoesUserHaveClusterAccess(
-						requestlog.NewHandler(a.HandleGetNotificationConfig, l),
-						mw.URLParam,
-						mw.QueryParam,
-					),
-					mw.URLParam,
-					mw.WriteAccess,
-				),
-			)
-
-			// /api/projects/{project_id}/helmrepos routes
-			r.Method(
-				"POST",
-				"/projects/{project_id}/helmrepos",
-				auth.DoesUserHaveProjectAccess(
-					auth.DoesUserHaveAWSIntegrationAccess(
-						auth.DoesUserHaveGCPIntegrationAccess(
-							requestlog.NewHandler(a.HandleCreateHelmRepo, l),
-							mw.URLParam,
-							mw.BodyParam,
-							true,
-						),
-						mw.URLParam,
-						mw.BodyParam,
-						true,
-					),
-					mw.URLParam,
-					mw.WriteAccess,
-				),
-			)
-
-			r.Method(
-				"GET",
-				"/projects/{project_id}/helmrepos",
-				auth.DoesUserHaveProjectAccess(
-					requestlog.NewHandler(a.HandleListProjectHelmRepos, l),
-					mw.URLParam,
-					mw.ReadAccess,
-				),
-			)
-
-			r.Method(
-				"GET",
-				"/projects/{project_id}/helmrepos/{helm_id}/charts",
-				auth.DoesUserHaveProjectAccess(
-					requestlog.NewHandler(a.HandleListHelmRepoCharts, l),
-					mw.URLParam,
-					mw.ReadAccess,
-				),
-			)
-
-			// /api/projects/{project_id}/registries routes
-			r.Method(
-				"POST",
-				"/projects/{project_id}/registries",
-				auth.DoesUserHaveProjectAccess(
-					auth.DoesUserHaveAWSIntegrationAccess(
-						auth.DoesUserHaveGCPIntegrationAccess(
-							auth.DoesUserHaveDOIntegrationAccess(
-								requestlog.NewHandler(a.HandleCreateRegistry, l),
-								mw.URLParam,
-								mw.BodyParam,
-								true,
-							),
-							mw.URLParam,
-							mw.BodyParam,
-							true,
-						),
-						mw.URLParam,
-						mw.BodyParam,
-						true,
-					),
-					mw.URLParam,
-					mw.WriteAccess,
-				),
-			)
-
-			r.Method(
-				"GET",
-				"/projects/{project_id}/registries",
-				auth.DoesUserHaveProjectAccess(
-					requestlog.NewHandler(a.HandleListProjectRegistries, l),
-					mw.URLParam,
-					mw.ReadAccess,
-				),
-			)
-
-			r.Method(
-				"POST",
-				"/projects/{project_id}/registries/{registry_id}",
-				auth.DoesUserHaveProjectAccess(
-					auth.DoesUserHaveRegistryAccess(
-						requestlog.NewHandler(a.HandleUpdateProjectRegistry, l),
-						mw.URLParam,
-						mw.URLParam,
-					),
-					mw.URLParam,
-					mw.ReadAccess,
-				),
-			)
-
-			r.Method(
-				"POST",
-				"/projects/{project_id}/registries/{registry_id}/repository",
-				auth.DoesUserHaveProjectAccess(
-					auth.DoesUserHaveRegistryAccess(
-						requestlog.NewHandler(a.HandleCreateRepository, l),
-						mw.URLParam,
-						mw.URLParam,
-					),
-					mw.URLParam,
-					mw.WriteAccess,
-				),
-			)
-
-			r.Method(
-				"GET",
-				"/projects/{project_id}/registries/ecr/{region}/token",
-				auth.DoesUserHaveProjectAccess(
-					requestlog.NewHandler(a.HandleGetProjectRegistryECRToken, l),
-					mw.URLParam,
-					mw.WriteAccess,
-				),
-			)
-
-			r.Method(
-				"GET",
-				"/projects/{project_id}/registries/gcr/token",
-				auth.DoesUserHaveProjectAccess(
-					requestlog.NewHandler(a.HandleGetProjectRegistryGCRToken, l),
-					mw.URLParam,
-					mw.WriteAccess,
-				),
-			)
-
-			r.Method(
-				"GET",
-				"/projects/{project_id}/registries/dockerhub/token",
-				auth.DoesUserHaveProjectAccess(
-					requestlog.NewHandler(a.HandleGetProjectRegistryDockerhubToken, l),
-					mw.URLParam,
-					mw.WriteAccess,
-				),
-			)
-
-			r.Method(
-				"GET",
-				"/projects/{project_id}/registries/docr/token",
-				auth.DoesUserHaveProjectAccess(
-					requestlog.NewHandler(a.HandleGetProjectRegistryDOCRToken, l),
-					mw.URLParam,
-					mw.WriteAccess,
-				),
-			)
-
-			r.Method(
-				"DELETE",
-				"/projects/{project_id}/registries/{registry_id}",
-				auth.DoesUserHaveProjectAccess(
-					auth.DoesUserHaveRegistryAccess(
-						requestlog.NewHandler(a.HandleDeleteProjectRegistry, l),
-						mw.URLParam,
-						mw.URLParam,
-					),
-					mw.URLParam,
-					mw.WriteAccess,
-				),
-			)
-
-			// /api/projects/{project_id}/registries/{registry_id}/repositories routes
-			r.Method(
-				"GET",
-				"/projects/{project_id}/registries/{registry_id}/repositories",
-				auth.DoesUserHaveProjectAccess(
-					auth.DoesUserHaveRegistryAccess(
-						requestlog.NewHandler(a.HandleListRepositories, l),
-						mw.URLParam,
-						mw.URLParam,
-					),
-					mw.URLParam,
-					mw.WriteAccess,
-				),
-			)
-
-			r.Method(
-				"GET",
-				// * is the repo name, which can itself be nested
-				// for example, for GCR this is project-id/repo
-				// need to use wildcard, see https://github.com/go-chi/chi/issues/243
-				"/projects/{project_id}/registries/{registry_id}/repositories/*",
-				auth.DoesUserHaveProjectAccess(
-					auth.DoesUserHaveRegistryAccess(
-						requestlog.NewHandler(a.HandleListImages, l),
-						mw.URLParam,
-						mw.URLParam,
-					),
-					mw.URLParam,
-					mw.ReadAccess,
-				),
-			)
-
-			// /api/projects/{project_id}/releases routes
-			r.Method(
-				"GET",
-				"/projects/{project_id}/releases",
-				auth.DoesUserHaveProjectAccess(
-					auth.DoesUserHaveClusterAccess(
-						requestlog.NewHandler(a.HandleListReleases, l),
-						mw.URLParam,
-						mw.QueryParam,
-					),
-					mw.URLParam,
-					mw.ReadAccess,
-				),
-			)
-
-			r.Method(
-				"GET",
-				"/projects/{project_id}/releases/{name}/{revision}/components",
-				auth.DoesUserHaveProjectAccess(
-					auth.DoesUserHaveClusterAccess(
-						requestlog.NewHandler(a.HandleGetReleaseComponents, l),
-						mw.URLParam,
-						mw.QueryParam,
-					),
-					mw.URLParam,
-					mw.ReadAccess,
-				),
-			)
-
-			r.Method(
-				"GET",
-				"/projects/{project_id}/releases/{name}/{revision}/controllers",
-				auth.DoesUserHaveProjectAccess(
-					auth.DoesUserHaveClusterAccess(
-						requestlog.NewHandler(a.HandleGetReleaseControllers, l),
-						mw.URLParam,
-						mw.QueryParam,
-					),
-					mw.URLParam,
-					mw.ReadAccess,
-				),
-			)
-
-			r.Method(
-				"GET",
-				"/projects/{project_id}/releases/{name}/{revision}/pods/all",
-				auth.DoesUserHaveProjectAccess(
-					auth.DoesUserHaveClusterAccess(
-						requestlog.NewHandler(a.HandleGetReleaseAllPods, l),
-						mw.URLParam,
-						mw.QueryParam,
-					),
-					mw.URLParam,
-					mw.ReadAccess,
-				),
-			)
-
-			r.Method(
-				"GET",
-				"/projects/{project_id}/releases/{name}/history",
-				auth.DoesUserHaveProjectAccess(
-					auth.DoesUserHaveClusterAccess(
-						requestlog.NewHandler(a.HandleListReleaseHistory, l),
-						mw.URLParam,
-						mw.QueryParam,
-					),
-					mw.URLParam,
-					mw.ReadAccess,
-				),
-			)
-
-			r.Method(
-				"GET",
-				"/projects/{project_id}/releases/{name}/webhook_token",
-				auth.DoesUserHaveProjectAccess(
-					auth.DoesUserHaveClusterAccess(
-						requestlog.NewHandler(a.HandleGetReleaseToken, l),
-						mw.URLParam,
-						mw.QueryParam,
-					),
-					mw.URLParam,
-					mw.ReadAccess,
-				),
-			)
-
-			r.Method(
-				"POST",
-				"/projects/{project_id}/releases/{name}/webhook_token",
-				auth.DoesUserHaveProjectAccess(
-					auth.DoesUserHaveClusterAccess(
-						requestlog.NewHandler(a.HandleCreateWebhookToken, l),
-						mw.URLParam,
-						mw.QueryParam,
-					),
-					mw.URLParam,
-					mw.WriteAccess,
-				),
-			)
-
-			r.Method(
-				"GET",
-				"/projects/{project_id}/releases/{name}/{revision}",
-				auth.DoesUserHaveProjectAccess(
-					auth.DoesUserHaveClusterAccess(
-						requestlog.NewHandler(a.HandleGetRelease, l),
-						mw.URLParam,
-						mw.QueryParam,
-					),
-					mw.URLParam,
-					mw.ReadAccess,
-				),
-			)
-
-			r.Method(
-				"GET",
-				"/projects/{project_id}/releases/{name}/steps",
-				auth.DoesUserHaveProjectAccess(
-					auth.DoesUserHaveClusterAccess(
-						requestlog.NewHandler(a.HandleGetReleaseSteps, l),
-						mw.URLParam,
-						mw.QueryParam,
-					),
-					mw.URLParam,
-					mw.ReadAccess,
-				),
-			)
-
-			r.Method(
-				"POST",
-				"/projects/{project_id}/releases/{name}/steps",
-				auth.DoesUserHaveProjectAccess(
-					auth.DoesUserHaveClusterAccess(
-						requestlog.NewHandler(a.HandleUpdateReleaseSteps, l),
-						mw.URLParam,
-						mw.BodyParam,
-					),
-					mw.URLParam,
-					mw.ReadAccess,
-				),
-			)
-
-			// /api/projects/{project_id}/gitrepos routes
-			r.Method(
-				"GET",
-				"/projects/{project_id}/gitrepos",
-				auth.DoesUserHaveProjectAccess(
-					requestlog.NewHandler(a.HandleListProjectGitRepos, l),
-					mw.URLParam,
-					mw.ReadAccess,
-				),
-			)
-
-			r.Method(
-				"GET",
-				"/projects/{project_id}/gitrepos/{installation_id}/repos",
-				auth.DoesUserHaveProjectAccess(
-					auth.DoesUserHaveGitInstallationAccess(
-						requestlog.NewHandler(a.HandleListRepos, l),
-						mw.URLParam,
-					),
-					mw.URLParam,
-					mw.ReadAccess,
-				),
-			)
-
-			r.Method(
-				"GET",
-				"/projects/{project_id}/gitrepos/{installation_id}/repos/{kind}/{owner}/{name}/branches",
-				auth.DoesUserHaveProjectAccess(
-					auth.DoesUserHaveGitInstallationAccess(
-						requestlog.NewHandler(a.HandleGetBranches, l),
-						mw.URLParam,
-					),
-					mw.URLParam,
-					mw.ReadAccess,
-				),
-			)
-
-			r.Method(
-				"GET",
-				"/projects/{project_id}/gitrepos/{installation_id}/repos/{kind}/{owner}/{name}/{branch}/buildpack/detect",
-				auth.DoesUserHaveProjectAccess(
-					auth.DoesUserHaveGitInstallationAccess(
-						requestlog.NewHandler(a.HandleDetectBuildpack, l),
-						mw.URLParam,
-					),
-					mw.URLParam,
-					mw.ReadAccess,
-				),
-			)
-
-			r.Method(
-				"GET",
-				"/projects/{project_id}/gitrepos/{installation_id}/repos/{kind}/{owner}/{name}/{branch}/contents",
-				auth.DoesUserHaveProjectAccess(
-					auth.DoesUserHaveGitInstallationAccess(
-						requestlog.NewHandler(a.HandleGetBranchContents, l),
-						mw.URLParam,
-					),
-					mw.URLParam,
-					mw.ReadAccess,
-				),
-			)
-
-			r.Method(
-				"GET",
-				"/projects/{project_id}/gitrepos/{installation_id}/repos/{kind}/{owner}/{name}/{branch}/procfile",
-				auth.DoesUserHaveProjectAccess(
-					auth.DoesUserHaveGitInstallationAccess(
-						requestlog.NewHandler(a.HandleGetProcfileContents, l),
-						mw.URLParam,
-					),
-					mw.URLParam,
-					mw.ReadAccess,
-				),
-			)
-
-			r.Method(
-				"GET",
-				"/projects/{project_id}/gitrepos/{installation_id}/repos/{kind}/{owner}/{name}/{branch}/tarball_url",
-				auth.DoesUserHaveProjectAccess(
-					auth.DoesUserHaveGitInstallationAccess(
-						requestlog.NewHandler(a.HandleGetRepoZIPDownloadURL, l),
-						mw.URLParam,
-					),
-					mw.URLParam,
-					mw.ReadAccess,
-				),
-			)
-
-			// /api/projects/{project_id}/k8s routes
-			r.Method(
-				"GET",
-				"/projects/{project_id}/k8s/namespaces",
-				auth.DoesUserHaveProjectAccess(
-					auth.DoesUserHaveClusterAccess(
-						requestlog.NewHandler(a.HandleListNamespaces, l),
-						mw.URLParam,
-						mw.QueryParam,
-					),
-					mw.URLParam,
-					mw.ReadAccess,
-				),
-			)
-
-			r.Method(
-				"POST",
-				"/projects/{project_id}/k8s/namespaces/create",
-				auth.DoesUserHaveProjectAccess(
-					auth.DoesUserHaveClusterAccess(
-						requestlog.NewHandler(a.HandleCreateNamespace, l),
-						mw.URLParam,
-						mw.QueryParam,
-					),
-					mw.URLParam,
-					mw.ReadAccess,
-				),
-			)
-
-			r.Method(
-				"DELETE",
-				"/projects/{project_id}/k8s/namespaces/delete",
-				auth.DoesUserHaveProjectAccess(
-					auth.DoesUserHaveClusterAccess(
-						requestlog.NewHandler(a.HandleDeleteNamespace, l),
-						mw.URLParam,
-						mw.QueryParam,
-					),
-					mw.URLParam,
-					mw.ReadAccess,
-				),
-			)
-
-			r.Method(
-				"GET",
-				"/projects/{project_id}/k8s/kubeconfig",
-				auth.DoesUserHaveProjectAccess(
-					auth.DoesUserHaveClusterAccess(
-						requestlog.NewHandler(a.HandleGetTemporaryKubeconfig, l),
-						mw.URLParam,
-						mw.QueryParam,
-					),
-					mw.URLParam,
-					mw.WriteAccess,
-				),
-			)
-
-			r.Method(
-				"GET",
-				"/projects/{project_id}/k8s/prometheus/detect",
-				auth.DoesUserHaveProjectAccess(
-					auth.DoesUserHaveClusterAccess(
-						requestlog.NewHandler(a.HandleDetectPrometheusInstalled, l),
-						mw.URLParam,
-						mw.QueryParam,
-					),
-					mw.URLParam,
-					mw.ReadAccess,
-				),
-			)
-
-			r.Method(
-				"GET",
-				"/projects/{project_id}/k8s/prometheus/ingresses",
-				auth.DoesUserHaveProjectAccess(
-					auth.DoesUserHaveClusterAccess(
-						requestlog.NewHandler(a.HandleListNGINXIngresses, l),
-						mw.URLParam,
-						mw.QueryParam,
-					),
-					mw.URLParam,
-					mw.ReadAccess,
-				),
-			)
-
-			r.Method(
-				"GET",
-				"/projects/{project_id}/k8s/metrics",
-				auth.DoesUserHaveProjectAccess(
-					auth.DoesUserHaveClusterAccess(
-						requestlog.NewHandler(a.HandleGetPodMetrics, l),
-						mw.URLParam,
-						mw.QueryParam,
-					),
-					mw.URLParam,
-					mw.ReadAccess,
-				),
-			)
-
-			r.Method(
-				"GET",
-				"/projects/{project_id}/k8s/{namespace}/pod/{name}/logs",
-				auth.DoesUserHaveProjectAccess(
-					auth.DoesUserHaveClusterAccess(
-						requestlog.NewHandler(a.HandleGetPodLogs, l),
-						mw.URLParam,
-						mw.QueryParam,
-					),
-					mw.URLParam,
-					mw.ReadAccess,
-				),
-			)
-
-			r.Method(
-				"GET",
-				"/projects/{project_id}/k8s/{namespace}/{chart}/{release_name}/jobs",
-				auth.DoesUserHaveProjectAccess(
-					auth.DoesUserHaveClusterAccess(
-						requestlog.NewHandler(a.HandleListJobsByChart, l),
-						mw.URLParam,
-						mw.QueryParam,
-					),
-					mw.URLParam,
-					mw.ReadAccess,
-				),
-			)
-
-			r.Method(
-				"GET",
-				"/projects/{project_id}/k8s/{namespace}/{name}/jobs/status",
-				auth.DoesUserHaveProjectAccess(
-					auth.DoesUserHaveClusterAccess(
-						requestlog.NewHandler(a.HandleGetJobStatus, l),
-						mw.URLParam,
-						mw.QueryParam,
-					),
-					mw.URLParam,
-					mw.ReadAccess,
-				),
-			)
-
-			r.Method(
-				"GET",
-				"/projects/{project_id}/k8s/jobs/{namespace}/{name}/pods",
-				auth.DoesUserHaveProjectAccess(
-					auth.DoesUserHaveClusterAccess(
-						requestlog.NewHandler(a.HandleListJobPods, l),
-						mw.URLParam,
-						mw.QueryParam,
-					),
-					mw.URLParam,
-					mw.ReadAccess,
-				),
-			)
-
-			r.Method(
-				"GET",
-				"/projects/{project_id}/k8s/{namespace}/ingress/{name}",
-				auth.DoesUserHaveProjectAccess(
-					auth.DoesUserHaveClusterAccess(
-						requestlog.NewHandler(a.HandleGetIngress, l),
-						mw.URLParam,
-						mw.QueryParam,
-					),
-					mw.URLParam,
-					mw.ReadAccess,
-				),
-			)
-
-			r.Method(
-				"GET",
-				"/projects/{project_id}/k8s/{kind}/status",
-				auth.DoesUserHaveProjectAccess(
-					auth.DoesUserHaveClusterAccess(
-						requestlog.NewHandler(a.HandleStreamControllerStatus, l),
-						mw.URLParam,
-						mw.QueryParam,
-					),
-					mw.URLParam,
-					mw.ReadAccess,
-				),
-			)
-
-			r.Method(
-				"GET",
-				"/projects/{project_id}/k8s/helm_releases",
-				auth.DoesUserHaveProjectAccess(
-					auth.DoesUserHaveClusterAccess(
-						requestlog.NewHandler(a.HandleStreamHelmReleases, l),
-						mw.URLParam,
-						mw.QueryParam,
-					),
-					mw.URLParam,
-					mw.ReadAccess,
-				),
-			)
-
-			r.Method(
-				"GET",
-				"/projects/{project_id}/k8s/pods",
-				auth.DoesUserHaveProjectAccess(
-					auth.DoesUserHaveClusterAccess(
-						requestlog.NewHandler(a.HandleListPods, l),
-						mw.URLParam,
-						mw.QueryParam,
-					),
-					mw.URLParam,
-					mw.ReadAccess,
-				),
-			)
-
-			r.Method(
-				"DELETE",
-				"/projects/{project_id}/k8s/pods/{namespace}/{name}",
-				auth.DoesUserHaveProjectAccess(
-					auth.DoesUserHaveClusterAccess(
-						requestlog.NewHandler(a.HandleDeletePod, l),
-						mw.URLParam,
-						mw.QueryParam,
-					),
-					mw.URLParam,
-					mw.WriteAccess,
-				),
-			)
-
-			r.Method(
-				"GET",
-				"/projects/{project_id}/k8s/pods/{namespace}/{name}/events/list",
-				auth.DoesUserHaveProjectAccess(
-					auth.DoesUserHaveClusterAccess(
-						requestlog.NewHandler(a.HandleListPodEvents, l),
-						mw.URLParam,
-						mw.QueryParam,
-					),
-					mw.URLParam,
-					mw.ReadAccess,
-				),
-			)
-
-			r.Method(
-				"POST",
-				"/projects/{project_id}/k8s/configmap/create",
-				auth.DoesUserHaveProjectAccess(
-					auth.DoesUserHaveClusterAccess(
-						requestlog.NewHandler(a.HandleCreateConfigMap, l),
-						mw.URLParam,
-						mw.QueryParam,
-					),
-					mw.URLParam,
-					mw.WriteAccess,
-				),
-			)
-
-			r.Method(
-				"DELETE",
-				"/projects/{project_id}/k8s/configmap/delete",
-				auth.DoesUserHaveProjectAccess(
-					auth.DoesUserHaveClusterAccess(
-						requestlog.NewHandler(a.HandleDeleteConfigMap, l),
-						mw.URLParam,
-						mw.QueryParam,
-					),
-					mw.URLParam,
-					mw.WriteAccess,
-				),
-			)
-
-			r.Method(
-				"GET",
-				"/projects/{project_id}/k8s/configmap",
-				auth.DoesUserHaveProjectAccess(
-					auth.DoesUserHaveClusterAccess(
-						requestlog.NewHandler(a.HandleGetConfigMap, l),
-						mw.URLParam,
-						mw.QueryParam,
-					),
-					mw.URLParam,
-					mw.ReadAccess,
-				),
-			)
-
-			r.Method(
-				"GET",
-				"/projects/{project_id}/k8s/configmap/list",
-				auth.DoesUserHaveProjectAccess(
-					auth.DoesUserHaveClusterAccess(
-						requestlog.NewHandler(a.HandleListConfigMaps, l),
-						mw.URLParam,
-						mw.QueryParam,
-					),
-					mw.URLParam,
-					mw.ReadAccess,
-				),
-			)
-
-			r.Method(
-				"POST",
-				"/projects/{project_id}/k8s/configmap/update",
-				auth.DoesUserHaveProjectAccess(
-					auth.DoesUserHaveClusterAccess(
-						requestlog.NewHandler(a.HandleUpdateConfigMap, l),
-						mw.URLParam,
-						mw.QueryParam,
-					),
-					mw.URLParam,
-					mw.WriteAccess,
-				),
-			)
-
-			r.Method(
-				"POST",
-				"/projects/{project_id}/k8s/configmap/rename",
-				auth.DoesUserHaveProjectAccess(
-					auth.DoesUserHaveClusterAccess(
-						requestlog.NewHandler(a.HandleRenameConfigMap, l),
-						mw.URLParam,
-						mw.QueryParam,
-					),
-					mw.URLParam,
-					mw.WriteAccess,
-				),
-			)
-
-			r.Method(
-				"DELETE",
-				"/projects/{project_id}/k8s/jobs/{namespace}/{name}",
-				auth.DoesUserHaveProjectAccess(
-					auth.DoesUserHaveClusterAccess(
-						requestlog.NewHandler(a.HandleDeleteJob, l),
-						mw.URLParam,
-						mw.QueryParam,
-					),
-					mw.URLParam,
-					mw.WriteAccess,
-				),
-			)
-
-			r.Method(
-				"POST",
-				"/projects/{project_id}/k8s/jobs/{namespace}/{name}/stop",
-				auth.DoesUserHaveProjectAccess(
-					auth.DoesUserHaveClusterAccess(
-						requestlog.NewHandler(a.HandleStopJob, l),
-						mw.URLParam,
-						mw.QueryParam,
-					),
-					mw.URLParam,
-					mw.WriteAccess,
-				),
-			)
-
-			// /api/projects/{project_id}/subdomain routes
-			r.Method(
-				"POST",
-				"/projects/{project_id}/k8s/subdomain",
-				auth.DoesUserHaveProjectAccess(
-					auth.DoesUserHaveClusterAccess(
-						requestlog.NewHandler(a.HandleCreateDNSRecord, l),
-						mw.URLParam,
-						mw.QueryParam,
-					),
-					mw.URLParam,
-					mw.WriteAccess,
-				),
-			)
-
-			// capabilities
-			r.Method(
-				"GET",
-				"/capabilities",
-				http.HandlerFunc(a.HandleGetCapabilities),
-			)
-
-			// welcome form
-			r.Method(
-				"GET",
-				"/welcome",
-				http.HandlerFunc(a.HandleWelcome),
-			)
-
-			// /api/projects/{project_id}/deploy routes
-			r.Method(
-				"POST",
-				"/projects/{project_id}/deploy/{name}/{version}",
-				auth.DoesUserHaveProjectAccess(
-					auth.DoesUserHaveClusterAccess(
-						requestlog.NewHandler(a.HandleDeployTemplate, l),
-						mw.URLParam,
-						mw.QueryParam,
-					),
-					mw.URLParam,
-					mw.WriteAccess,
-				),
-			)
-
-			r.Method(
-				"POST",
-				"/projects/{project_id}/deploy/addon/{name}/{version}",
-				auth.DoesUserHaveProjectAccess(
-					auth.DoesUserHaveClusterAccess(
-						requestlog.NewHandler(a.HandleDeployAddon, l),
-						mw.URLParam,
-						mw.QueryParam,
-					),
-					mw.URLParam,
-					mw.WriteAccess,
-				),
-			)
-		})
-
-		// Create group for long-running Helm operations
-		r.Group(func(r chi.Router) {
-			r.Use(middleware.Timeout(300 * time.Second))
-
-			r.Method(
-				"POST",
-				"/projects/{project_id}/releases/{name}/rollback",
-				auth.DoesUserHaveProjectAccess(
-					auth.DoesUserHaveClusterAccess(
-						requestlog.NewHandler(a.HandleRollbackRelease, l),
-						mw.URLParam,
-						mw.QueryParam,
-					),
-					mw.URLParam,
-					mw.WriteAccess,
-				),
-			)
-
-			r.Method(
-				"POST",
-				"/webhooks/deploy/{token}",
-				requestlog.NewHandler(a.HandleReleaseDeployWebhook, l),
-			)
-
-			r.Method(
-				"POST",
-				"/projects/{project_id}/delete/{name}",
-				auth.DoesUserHaveProjectAccess(
-					auth.DoesUserHaveClusterAccess(
-						requestlog.NewHandler(a.HandleUninstallTemplate, l),
-						mw.URLParam,
-						mw.QueryParam,
-					),
-					mw.URLParam,
-					mw.WriteAccess,
-				),
-			)
-
-			r.Method(
-				"POST",
-				"/projects/{project_id}/releases/{name}/upgrade",
-				auth.DoesUserHaveProjectAccess(
-					auth.DoesUserHaveClusterAccess(
-						requestlog.NewHandler(a.HandleUpgradeRelease, l),
-						mw.URLParam,
-						mw.QueryParam,
-					),
-					mw.URLParam,
-					mw.WriteAccess,
-				),
-			)
-
-			r.Method(
-				"POST",
-				"/projects/{project_id}/releases/image/update/batch",
-				auth.DoesUserHaveProjectAccess(
-					auth.DoesUserHaveClusterAccess(
-						requestlog.NewHandler(a.HandleReleaseUpdateJobImages, l),
-						mw.URLParam,
-						mw.QueryParam,
-					),
-					mw.URLParam,
-					mw.WriteAccess,
-				),
-			)
-		})
-	})
-
-	staticFilePath := a.ServerConf.StaticFilePath
-
-	fs := http.FileServer(http.Dir(staticFilePath))
-
-	r.Get("/*", func(w http.ResponseWriter, r *http.Request) {
-		if _, err := os.Stat(staticFilePath + r.RequestURI); os.IsNotExist(err) {
-			w.Header().Set("Cache-Control", "no-cache")
-
-			http.StripPrefix(r.URL.Path, fs).ServeHTTP(w, r)
-		} else {
-			// Set static files involving html, js, or empty cache to "no-cache", which means they must be validated
-			// for changes before the browser uses the cache
-			if base := path.Base(r.URL.Path); strings.Contains(base, "html") || strings.Contains(base, "js") || base == "." || base == "/" {
-				w.Header().Set("Cache-Control", "no-cache")
-			}
-
-			fs.ServeHTTP(w, r)
-		}
-	})
-
-	return r
-}